diff --git a/.opencode-version b/.opencode-version index c88935c5aa..64a7cf0c30 100644 --- a/.opencode-version +++ b/.opencode-version @@ -1 +1 @@ -v7.3.45 +v7.3.46 diff --git a/bun.lock b/bun.lock index 7ae00f9fb6..d24aea1a0a 100644 --- a/bun.lock +++ b/bun.lock @@ -33,7 +33,7 @@ }, "packages/clickzetta-js": { "name": "clickzetta-js", - "version": "0.1.0", + "version": "7.2.49", "devDependencies": { "typescript": "catalog:", }, @@ -45,22 +45,50 @@ "opencode": "./bin/opencode", }, "dependencies": { + "@ai-sdk/alibaba": "1.0.17", + "@ai-sdk/amazon-bedrock": "4.0.96", + "@ai-sdk/anthropic": "3.0.71", + "@ai-sdk/azure": "3.0.49", + "@ai-sdk/cerebras": "2.0.54", + "@ai-sdk/cohere": "3.0.27", + "@ai-sdk/deepinfra": "2.0.41", + "@ai-sdk/gateway": "3.0.104", + "@ai-sdk/google": "3.0.63", + "@ai-sdk/google-vertex": "4.0.112", + "@ai-sdk/groq": "3.0.31", + "@ai-sdk/mistral": "3.0.27", + "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai-compatible": "2.0.48", + "@ai-sdk/perplexity": "3.0.26", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.23", + "@ai-sdk/togetherai": "2.0.41", + "@ai-sdk/vercel": "2.0.39", + "@ai-sdk/xai": "3.0.92", + "@aws-sdk/credential-providers": "3.993.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", + "@kilocode/kilo-gateway": "workspace:*", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", + "@openrouter/ai-sdk-provider": "2.9.0", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", + "ai-gateway-provider": "3.1.2", "cross-spawn": "catalog:", "effect": "catalog:", + "gitlab-ai-provider": "6.6.0", "glob": "13.0.5", + "google-auth-library": "10.5.0", + "immer": "11.1.4", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "rotating-file-stream": "3.2.9", "semver": "^7.6.3", + "venice-ai-sdk-provider": "2.0.1", "xdg-basedir": "5.1.0", "zod": "catalog:", }, @@ -75,7 +103,7 @@ }, "packages/czcode-lakehouse": { "name": "@czcode/lakehouse", - "version": "0.1.0", + "version": "7.2.49", "dependencies": { "@kilocode/plugin": "workspace:*", "@opencode-ai/core": "workspace:*", @@ -87,6 +115,41 @@ "typescript": "catalog:", }, }, + "packages/http-recorder": { + "name": "@opencode-ai/http-recorder", + "version": "7.2.49", + "dependencies": { + "@effect/platform-node": "catalog:", + "effect": "catalog:", + }, + "devDependencies": { + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@typescript/native-preview": "catalog:", + }, + }, + "packages/kilo-console": { + "name": "@kilocode/kilo-console", + "version": "7.2.49", + "dependencies": { + "@kilocode/kilo-web-ui": "workspace:*", + "@kilocode/sdk": "workspace:*", + "@lottiefiles/dotlottie-web": "0.74.0", + "@opencode-ai/ui": "workspace:*", + "@solidjs/router": "catalog:", + "ghostty-web": "0.4.0", + "solid-js": "catalog:", + "yaml": "2.8.3", + }, + "devDependencies": { + "@tsconfig/node22": "catalog:", + "@types/bun": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-solid": "catalog:", + }, + }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", "version": "7.2.49", @@ -114,7 +177,7 @@ "postcss": "^8.5.6", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", - "vitest": "^3.2.3", + "vitest": "4.1.0", }, }, "packages/kilo-gateway": { @@ -123,12 +186,13 @@ "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.71", - "@ai-sdk/openai": "3.0.48", - "@ai-sdk/openai-compatible": "2.0.37", + "@ai-sdk/mistral": "3.0.27", + "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai-compatible": "2.0.48", "@clack/prompts": "1.0.0-alpha.1", "@kilocode/plugin": "workspace:*", "@kilocode/sdk": "workspace:*", - "@openrouter/ai-sdk-provider": "2.8.1", + "@openrouter/ai-sdk-provider": "2.9.0", "ai": "catalog:", "open": "10.1.2", "zod": "catalog:", @@ -268,6 +332,7 @@ "@kilocode/kilo-ui": "workspace:*", "@kilocode/sdk": "workspace:*", "@opencode-ai/ui": "workspace:*", + "@pierre/diffs": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "@xterm/addon-clipboard": "0.2.0", "@xterm/addon-fit": "0.11.0", @@ -323,11 +388,43 @@ "vite-plugin-solid": "2.11.10", }, }, + "packages/kilo-web-ui": { + "name": "@kilocode/kilo-web-ui", + "version": "7.2.49", + "dependencies": { + "@kilocode/kilo-ui": "workspace:*", + "@kobalte/core": "catalog:", + "solid-js": "catalog:", + }, + "devDependencies": { + "@tsconfig/node22": "catalog:", + "@types/bun": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "catalog:", + }, + }, + "packages/llm": { + "name": "@opencode-ai/llm", + "version": "7.2.49", + "dependencies": { + "@smithy/eventstream-codec": "4.2.14", + "@smithy/util-utf8": "4.2.2", + "aws4fetch": "1.0.20", + "effect": "catalog:", + }, + "devDependencies": { + "@clack/prompts": "1.0.0-alpha.1", + "@effect/platform-node": "catalog:", + "@opencode-ai/http-recorder": "workspace:*", + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@typescript/native-preview": "catalog:", + }, + }, "packages/opencode": { "name": "@kilocode/cli", "version": "7.2.49", "bin": { - "czcode": "./bin/kilo", "kilo": "./bin/kilo", "kilocode": "./bin/kilo", }, @@ -339,7 +436,7 @@ "@ai-sdk/amazon-bedrock": "4.0.96", "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/azure": "3.0.49", - "@ai-sdk/cerebras": "2.0.41", + "@ai-sdk/cerebras": "2.0.54", "@ai-sdk/cohere": "3.0.27", "@ai-sdk/deepinfra": "2.0.41", "@ai-sdk/gateway": "3.0.104", @@ -348,13 +445,13 @@ "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.27", "@ai-sdk/openai": "3.0.53", - "@ai-sdk/openai-compatible": "2.0.41", + "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.92", "@aws-sdk/credential-providers": "3.1025.0", "@clack/prompts": "1.0.0-alpha.1", "@czcode/lakehouse": "workspace:*", @@ -370,6 +467,7 @@ "@kilocode/kilo-indexing": "workspace:*", "@kilocode/kilo-telemetry": "workspace:*", "@kilocode/plugin": "workspace:*", + "@kilocode/plugin-atomic-chat": "workspace:*", "@kilocode/sdk": "workspace:*", "@lydell/node-pty": "catalog:", "@modelcontextprotocol/sdk": "1.29.0", @@ -380,16 +478,21 @@ "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", "@opencode-ai/script": "workspace:*", - "@openrouter/ai-sdk-provider": "2.8.1", + "@opencode-ai/ui": "workspace:*", + "@openrouter/ai-sdk-provider": "2.9.0", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentui/core": "catalog:", + "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", + "@secretlint/core": "10.2.2", + "@secretlint/secretlint-rule-preset-recommend": "10.2.2", + "@silvia-odwyer/photon-node": "0.3.4", "@solid-primitives/event-bus": "1.1.2", "@solid-primitives/scheduled": "1.5.2", "@standard-schema/spec": "1.0.0", @@ -414,6 +517,7 @@ "gray-matter": "4.0.3", "hono": "catalog:", "hono-openapi": "catalog:", + "htmlparser2": "8.0.2", "iconv-lite": "0.7.2", "ignore": "7.0.5", "immer": "11.1.4", @@ -472,6 +576,7 @@ "@types/bun": "catalog:", "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", + "@types/node": "catalog:", "@types/npm-package-arg": "6.1.4", "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", @@ -498,6 +603,7 @@ }, "devDependencies": { "@opentui/core": "catalog:", + "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", @@ -505,14 +611,30 @@ "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.2.2", - "@opentui/solid": ">=0.2.2", + "@opentui/core": ">=0.2.11", + "@opentui/keymap": ">=0.2.11", + "@opentui/solid": ">=0.2.11", }, "optionalPeers": [ "@opentui/core", + "@opentui/keymap", "@opentui/solid", ], }, + "packages/plugin-atomic-chat": { + "name": "@kilocode/plugin-atomic-chat", + "version": "7.2.49", + "dependencies": { + "@kilocode/plugin": "workspace:*", + }, + "devDependencies": { + "@tsconfig/node22": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "catalog:", + "vitest": "^4.0.16", + }, + }, "packages/script": { "name": "@opencode-ai/script", "version": "7.2.49", @@ -541,7 +663,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "7.2.30", + "version": "7.2.49", "bin": { "opencode": "./bin/opencode", }, @@ -639,15 +761,24 @@ }, }, }, + "trustedDependencies": [ + "esbuild", + "tree-sitter-powershell", + "protobufjs", + "web-tree-sitter", + "tree-sitter-bash", + ], "patchedDependencies": { + "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch", "stream-chat@9.38.0": "patches/stream-chat@9.38.0.patch", - "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", + "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", }, "overrides": { "@effect/platform-node-shared": "4.0.0-beta.46", "@opentui/core": "catalog:", + "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", "@types/bun": "catalog:", "@types/node": "catalog:", @@ -667,16 +798,17 @@ }, "catalog": { "@cloudflare/workers-types": "4.20251008.0", - "@effect/opentelemetry": "4.0.0-beta.57", - "@effect/platform-node": "4.0.0-beta.57", + "@effect/opentelemetry": "4.0.0-beta.65", + "@effect/platform-node": "4.0.0-beta.65", "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", "@lydell/node-pty": "1.2.0-beta.10", "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@opentui/core": "0.2.2", - "@opentui/solid": "0.2.2", + "@opentui/core": "0.2.11", + "@opentui/keymap": "0.2.11", + "@opentui/solid": "0.2.11", "@pierre/diffs": "1.1.22", "@playwright/test": "1.59.1", "@solid-primitives/storage": "4.3.3", @@ -686,10 +818,10 @@ "@tailwindcss/vite": "4.1.11", "@tsconfig/bun": "1.0.9", "@tsconfig/node22": "22.0.2", - "@types/bun": "1.3.12", + "@types/bun": "1.3.14", "@types/cross-spawn": "6.0.6", "@types/luxon": "3.7.1", - "@types/node": "24.12.2", + "@types/node": "24.12.4", "@types/semver": "7.7.1", "@typescript/native-preview": "7.0.0-dev.20260316.1", "ai": "6.0.168", @@ -698,7 +830,7 @@ "dompurify": "3.4.2", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.59", + "effect": "4.0.0-beta.65", "fuzzysort": "3.1.0", "hono": "4.12.12", "hono-openapi": "1.1.2", @@ -745,7 +877,7 @@ "@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "https://registry.npmmirror.com/@ai-sdk/azure/-/azure-3.0.49.tgz", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="], - "@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.41", "https://registry.npmmirror.com/@ai-sdk/cerebras/-/cerebras-2.0.41.tgz", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kDMEpjaRdRXIUi1EH8WHwLRahyDTYv9SAJnP6VCCeq8X+tVqZbMLCqqxSG5dRknrI65ucjvzQt+FiDKTAa7AHg=="], + "@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.54", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-m+oLYlZUA0j5aeE8BrjDd2DSCx6HujqPB2C0YjQF7iGQ4sTCCmhHfH2o0uY7vlSPIJpA2ak6YyF7R14a5dq8fw=="], "@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.27", "https://registry.npmmirror.com/@ai-sdk/cohere/-/cohere-3.0.27.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OqcCq2PiFY1dbK/0Ck45KuvE8jfdxRuuAE9Y5w46dAk6U+9vPOeg1CDcmR+ncqmrYrhRl3nmyDttyDahyjCzAw=="], @@ -771,7 +903,7 @@ "@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-3.0.53.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], - "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.41.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.48.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="], "@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.26", "https://registry.npmmirror.com/@ai-sdk/perplexity/-/perplexity-3.0.26.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dXzrVsLR5f6tr+U04jq4AXoRroGFBTvODnLgss0SWbzNjGGQg3XqtQ9j7rCLo6o8qbYGuAHvqUrIpUCuiscuFg=="], @@ -783,7 +915,7 @@ "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "https://registry.npmmirror.com/@ai-sdk/vercel/-/vercel-2.0.39.tgz", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="], - "@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "https://registry.npmmirror.com/@ai-sdk/xai/-/xai-3.0.82.tgz", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + "@ai-sdk/xai": ["@ai-sdk/xai@3.0.92", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yqRMdEVfSkZCXs5mqeKfYOuQElo93igwz/4zWbYkhZWAW7tDh+5uqRef6MtQVYF/bTC1EsdbeO9DtsOtAZMOLA=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -1055,9 +1187,9 @@ "@effect/language-service": ["@effect/language-service@0.84.2", "https://registry.npmmirror.com/@effect/language-service/-/language-service-0.84.2.tgz", { "bin": { "effect-language-service": "cli.js" } }, "sha512-l04qNxpiA8rY5yXWckRPJ7Mk5MNerXuNymSFf+IdflfI5i8jgL1bpBNLuP6ijg7wgjdHc/KmTnCj2kT0SCntuA=="], - "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.57", "https://registry.npmmirror.com/@effect/opentelemetry/-/opentelemetry-4.0.0-beta.57.tgz", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.57" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-gdjZPEP0QQg4qmI1vd+443kheeQZKytrjJIzCJncy6ZEpyk/SfrqeStLqLXdTRcms3IB0ls0vOV7KNq7YmBRVA=="], + "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.65", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.65" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-0CD2fSsXrDM7FP2WFkbGJO1DwMqWR3UKHh6oBDXPHAPA+RsJSKoh3pLQsbQfldLuKnhOy87Bv0v9r9IdrIHCQw=="], - "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.57", "https://registry.npmmirror.com/@effect/platform-node/-/platform-node-4.0.0-beta.57.tgz", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.57", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.57", "ioredis": "^5.7.0" } }, "sha512-la0xxPSAYOsY0d+uVxEBxok3jYB31iPQmIaZZRUj2SNWqcGGHJc6KorKtI8guqSLuv9FGZ255kBWXRbG6hMeeg=="], + "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.65", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.65", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.65", "ioredis": "^5.7.0" } }, "sha512-QQy3KRcMwP0TngQdfQGl2u1zp03B7k7DuF5SNS8aZhD0dDBpKZpCwFad1ODY5qdY3ycPgMwBwKRRK7y/aw0C9w=="], "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.46", "https://registry.npmmirror.com/@effect/platform-node-shared/-/platform-node-shared-4.0.0-beta.46.tgz", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.46" } }, "sha512-Yzci82XbZ1W3tuiownsJawrJZTGeTrTZKLD0uxdBWCBzlVyqDwoSwRwO5qh33DurJj9B7iS8MDf14fpGRBPNGQ=="], @@ -1283,6 +1415,8 @@ "@kilocode/cli": ["@kilocode/cli@workspace:packages/opencode"], + "@kilocode/kilo-console": ["@kilocode/kilo-console@workspace:packages/kilo-console"], + "@kilocode/kilo-docs": ["@kilocode/kilo-docs@workspace:packages/kilo-docs"], "@kilocode/kilo-gateway": ["@kilocode/kilo-gateway@workspace:packages/kilo-gateway"], @@ -1297,8 +1431,12 @@ "@kilocode/kilo-ui": ["@kilocode/kilo-ui@workspace:packages/kilo-ui"], + "@kilocode/kilo-web-ui": ["@kilocode/kilo-web-ui@workspace:packages/kilo-web-ui"], + "@kilocode/plugin": ["@kilocode/plugin@workspace:packages/plugin"], + "@kilocode/plugin-atomic-chat": ["@kilocode/plugin-atomic-chat@workspace:packages/plugin-atomic-chat"], + "@kilocode/sdk": ["@kilocode/sdk@workspace:packages/sdk/js"], "@kobalte/core": ["@kobalte/core@0.13.11", "https://registry.npmmirror.com/@kobalte/core/-/core-0.13.11.tgz", { "dependencies": { "@floating-ui/dom": "^1.5.1", "@internationalized/date": "^3.4.0", "@internationalized/number": "^3.2.1", "@kobalte/utils": "^0.9.1", "@solid-primitives/props": "^3.1.8", "@solid-primitives/resize-observer": "^2.0.26", "solid-presence": "^0.1.8", "solid-prevent-scroll": "^0.1.4" }, "peerDependencies": { "solid-js": "^1.8.15" } }, "sha512-hK7TYpdib/XDb/r/4XDBFaO9O+3ZHz4ZWryV4/3BfES+tSQVgg2IJupDnztKXB0BqbSRy/aWlHKw1SPtNPYCFQ=="], @@ -1327,6 +1465,8 @@ "@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "https://registry.npmmirror.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="], + "@lottiefiles/dotlottie-web": ["@lottiefiles/dotlottie-web@0.74.0", "", {}, "sha512-rG12+dJSVhQDdleGr9epR7zU/UJ6hh8jzBDHAurClHP5t5dSrOh3eP3UCFxolpVPcobaRVYsBnYg4HQokQewpg=="], + "@lukeed/ms": ["@lukeed/ms@2.0.2", "https://registry.npmmirror.com/@lukeed/ms/-/ms-2.0.2.tgz", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="], "@lydell/node-pty": ["@lydell/node-pty@1.2.0-beta.10", "https://registry.npmmirror.com/@lydell/node-pty/-/node-pty-1.2.0-beta.10.tgz", { "optionalDependencies": { "@lydell/node-pty-darwin-arm64": "1.2.0-beta.10", "@lydell/node-pty-darwin-x64": "1.2.0-beta.10", "@lydell/node-pty-linux-arm64": "1.2.0-beta.10", "@lydell/node-pty-linux-x64": "1.2.0-beta.10", "@lydell/node-pty-win32-arm64": "1.2.0-beta.10", "@lydell/node-pty-win32-x64": "1.2.0-beta.10" } }, "sha512-Fv+A3+MZVA8qhkBIZsM1E6dCdHNMyXXz22mAYiMWd03LlyK///F3OH6CKPX9mj4id7LUlxpr45yPzyBVy9aDPw=="], @@ -1465,6 +1605,10 @@ "@opencode-ai/core": ["@opencode-ai/core@workspace:packages/core"], + "@opencode-ai/http-recorder": ["@opencode-ai/http-recorder@workspace:packages/http-recorder"], + + "@opencode-ai/llm": ["@opencode-ai/llm@workspace:packages/llm"], + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.15.10", "https://registry.npmmirror.com/@opencode-ai/plugin/-/plugin-1.15.10.tgz", { "dependencies": { "@opencode-ai/sdk": "1.15.10", "effect": "4.0.0-beta.66", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.2.15", "@opentui/keymap": ">=0.2.15", "@opentui/solid": ">=0.2.15" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-V2p7CvpBtKWB+FID7Dl1y0Ci02zUT40A9b2RD9R9BOiuD8ZcKhHWov+irN0xVJA0Eg6OhEBfA0lPKRn1FNKPlw=="], "@opencode-ai/script": ["@opencode-ai/script@workspace:packages/script"], @@ -1477,7 +1621,7 @@ "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"], - "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "https://registry.npmmirror.com/@openrouter/ai-sdk-provider/-/ai-sdk-provider-2.8.1.tgz", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.9.0", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Seva+NCa0WUQnJIUE5GzHsUv1WTIeyqwz0ELl2VtS6NP+eF+77yCXGFVOMbvoCM7QMjlnhv7931e89R+8pJdcQ=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -1507,21 +1651,23 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "https://registry.npmmirror.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.2.2", "https://registry.npmmirror.com/@opentui/core/-/core-0.2.2.tgz", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.2", "@opentui/core-darwin-x64": "0.2.2", "@opentui/core-linux-arm64": "0.2.2", "@opentui/core-linux-x64": "0.2.2", "@opentui/core-win32-arm64": "0.2.2", "@opentui/core-win32-x64": "0.2.2" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-wxg1CD58SVrowu+WgbhZNi3UP/wWxPio2Kj2IeTjomoIE+6EXLxR8eCCxHYVuQUd9E4fknrKkY5HmiSsp6oPow=="], + "@opentui/core": ["@opentui/core@0.2.11", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.11", "@opentui/core-darwin-x64": "0.2.11", "@opentui/core-linux-arm64": "0.2.11", "@opentui/core-linux-x64": "0.2.11", "@opentui/core-win32-arm64": "0.2.11", "@opentui/core-win32-x64": "0.2.11" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-X0zLmcDEvMrPzWYp769I7VEVb+og38vaete9tGZXu9HnJgu/paPUUplUT+6denBQccr2qx1rBYV6EtgbBpLEyw=="], + + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-h2MXtE2Cu3XlKVoQMXthnbhleO68zGXkoh/r1Q5pCoZh6RuXqns5/94D/aZThXBWwzPuEoyarMlxxR9OqrpvHw=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.2", "https://registry.npmmirror.com/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.2.2.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-tY5n3ZRQx+b0kyhQJJLsyJMeZ+0w4FV37YZc/Qqv3qvOqE9kZPw/7adR77FYwWDm/7fax94mLMrR8Y5bKUkDmw=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-Y0jbPClnOBTPSIy+2THG86MTqIG/jGFlOOKuw4JfCDqEjPBM3pLWIHnJb3WxHRi2LlvfyBxvrUTXWlW6JpI0QQ=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.2", "https://registry.npmmirror.com/@opentui/core-darwin-x64/-/core-darwin-x64-0.2.2.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-W/R7OnqY30FXcTG0tiP2JkQFmgtYbIte5afQ5PC12TliRoee1RqG3iCG6kY1jxW+3Vg6jge88uiSjUEDpeV2gA=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-blQyyuTaW4q/OQ3whs7Kt7GCXhBUR5EQHHDdjOqQAr0HYpohUa6sbHMbiBcX2Ehc9ZWwtiaOoWiyZ5YXy2SAvg=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.2", "https://registry.npmmirror.com/@opentui/core-linux-arm64/-/core-linux-arm64-0.2.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1pzTYFEZauYuw6AGycw2TYGtAlZVGjuUtSdxH1fP51kBPS3oVWduUY2j7GKREz3SU5NulvO2Wc6HWsm3feMqwQ=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-0nEB5+MgzQRYiVcQd1vHXPWNPWGh4JEmQTJKyG3OHnTzPaJ1FVSQ/V71ECyRSl3ymY3F+U0eW9cFgw1hCieK2w=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.2", "https://registry.npmmirror.com/@opentui/core-linux-x64/-/core-linux-x64-0.2.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ucVwUtUYeOYGVFPBLbPoxzbrPdhD0PDyKNQ2X4n1AJ9jlQX4gqBZRcXMEF8hiXDjFxsikZwef7De0ciCcWvAMg=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-+KKH77fzm0qF8py9G2pU32DzB1bAgDMfBajrs7gKL5NtSEnknrwfh7hIs/tq41aF6j9zvIzgtykByh26tcjFog=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.2", "https://registry.npmmirror.com/@opentui/core-win32-arm64/-/core-win32-arm64-0.2.2.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-MPhYdJNdxmC5Bqsq6sis/+VkjRgkEjm+bQ1Tl++NSKLuiTU32Re0ImcZlgHbe+LZtZoGMZHVSgZlkGd3oYXO2g=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.11", "", { "os": "win32", "cpu": "x64" }, "sha512-dMmb9DX0W0HWadLdgciMbonqIc1xdcKiVmaQSYxw5eGCzFRPZIOrKHByesP+2ipkMuLx85W/MJUFal/lW8XSNg=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.2", "https://registry.npmmirror.com/@opentui/core-win32-x64/-/core-win32-x64-0.2.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-19BroLfn2h0RDYfJS5o96Fc8kYCDhRBcseIXtHIkoKIsKMxx62KiDLo/byVye6rp+yQRRB7Xkd2uWqsbdiWo9w=="], + "@opentui/keymap": ["@opentui/keymap@0.2.11", "", { "dependencies": { "@opentui/core": "0.2.11" }, "peerDependencies": { "@opentui/react": "0.2.11", "@opentui/solid": "0.2.11", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-pCrJrY3mTuXdDaaRneId1JsJCtGE+7prTtWihzOLZzVJTJYyYtT38gMI7MpyAoloVDfEL5cTe8C+v7wv+IYREw=="], - "@opentui/solid": ["@opentui/solid@0.2.2", "https://registry.npmmirror.com/@opentui/solid/-/solid-0.2.2.tgz", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.2", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-ZBVfCoVAhcUGQWPAWOTdzuVldMaRkuPpCu4U1VZCqmIw9DtbCuiVr0WnDocDxKhJLbTu8bl3qEWtVCf6lTSi3w=="], + "@opentui/solid": ["@opentui/solid@0.2.11", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.11", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-M3WHxBFORHVE0yqMJYpi9PfjXWlnRTw/LYuBhZaJv0HTo+zTs60P/ukGcwnHDWnMpTGf3BH9x0Yi2dIqjHRY6Q=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "https://registry.npmmirror.com/@oslojs/asn1/-/asn1-1.0.0.tgz", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -1841,6 +1987,8 @@ "@sigstore/verify": ["@sigstore/verify@3.1.1", "https://registry.npmmirror.com/@sigstore/verify/-/verify-3.1.1.tgz", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA=="], + "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], + "@simple-git/args-pathspec": ["@simple-git/args-pathspec@1.0.3", "https://registry.npmmirror.com/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", {}, "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA=="], "@simple-git/argv-parser": ["@simple-git/argv-parser@1.1.1", "https://registry.npmmirror.com/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", { "dependencies": { "@simple-git/args-pathspec": "^1.0.3" } }, "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw=="], @@ -1853,7 +2001,7 @@ "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.4", "https://registry.npmmirror.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-vKW0MEFRU4Y3MkVZUkpJm+g9qyPGLCXhc0YLggUdSdBB4g7IaSSsCE75P9rBXyWHrXY1UYSQUl8/DwsTR7QciA=="], - "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.3.4", "https://registry.npmmirror.com/@smithy/eventstream-codec/-/eventstream-codec-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-uI00gSvwvBEiVuqpHFcMPGXnVh1XQYuqlQREIX/GUmuwNNTdGRliUI8R71tbfwNLYP7t8E/HVqdohvjoWU13iA=="], + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="], "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.3.4", "https://registry.npmmirror.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-9szC3PfHhYSvWA98CIrD6rB8jS60tfKOPvDlzyD87gsDm8KDnsSpXnwPO1J3bPxg0tWE6Ljzk2YzZV2GBe3nUQ=="], @@ -1909,7 +2057,7 @@ "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.3.4", "https://registry.npmmirror.com/@smithy/util-body-length-node/-/util-body-length-node-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-e3pKOHP/UjTV4/2gMdjcgelvX8DGS6Yy3jSLWh47HvsyeD0fc/V4kkSYfhOjEnV4CizPn9gQojj2q9MiZQcJDg=="], - "@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.4.8", "", { "dependencies": { "@smithy/core": "^3.29.3", "tslib": "^2.6.2" } }, "sha512-v89h2SviTW7rJ+KCyCDnXRSNbm/wbEdALdErm9UWpDsxHzq9F4mJn2uYiEUEWL81oudj0oua3xaGS68eHbCfkg=="], "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.4.4", "https://registry.npmmirror.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.4.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-/TWNfyCtJHHIS5taeOQ1qcMUCr5xPqdFntDL5+Sp8sjGj29ZaFUUxlCP+6V//J7MhHZZ2PIe2kMh1YdOpaEPnA=="], @@ -1917,13 +2065,15 @@ "@smithy/util-endpoints": ["@smithy/util-endpoints@3.5.4", "https://registry.npmmirror.com/@smithy/util-endpoints/-/util-endpoints-3.5.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-RtzPUniH4R49dG8X2MeOi9UzcNwh8C8lEADOGItnAMifxljQgCbuUOpvciX7EnEEJ5H2T2AXvEdOuXSe0bKdaQ=="], + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.4.8", "", { "dependencies": { "@smithy/core": "^3.29.3", "tslib": "^2.6.2" } }, "sha512-7U8V8aMAx5U7EFghoHfj0mch+AUSFEm/+u6ah5Gidp29vWzMtoOGUwD83Krt0VYNyZd1j67k1sH/UZLai/WLNw=="], + "@smithy/util-middleware": ["@smithy/util-middleware@4.3.4", "https://registry.npmmirror.com/@smithy/util-middleware/-/util-middleware-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-jzWo5fD5FYdGlfqx+kpp5BoOSG+TYQczYY6Ue2QX4linDq+5q6t2/RtO53nABOZjD+qYSSaVd9RalyMIPbxk9Q=="], "@smithy/util-retry": ["@smithy/util-retry@4.4.4", "https://registry.npmmirror.com/@smithy/util-retry/-/util-retry-4.4.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-4upfJJ+jayyqd523zopC5Ad7XxMp+rpeiqh0QtiZGBvdBB7KBBtHVEtraHNnlzkQuytvkU5yyg6Ckf3ApJ3A5Q=="], "@smithy/util-stream": ["@smithy/util-stream@4.6.4", "https://registry.npmmirror.com/@smithy/util-stream/-/util-stream-4.6.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-mkc/JN/fPiaHBAhhp7LbwAQz6RFjrCkYZ4F3OK2ZAWbmkjDQmAyNUmoDcQDVGWF9U+13+fWPszCXFHLP/8NnAA=="], - "@smithy/util-utf8": ["@smithy/util-utf8@4.3.4", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-s8lfXcv+5C2GjBwGUBqFLgNmhyp9/n4TSKbOzKlIqJ/x0L/zwIxjNBC6DN4xUy59NvOrsiZI1t3tWi4ADUDyNw=="], + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], "@smithy/util-waiter": ["@smithy/util-waiter@4.4.4", "https://registry.npmmirror.com/@smithy/util-waiter/-/util-waiter-4.4.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-Qt+W1pLeV/gmsXXUKbcolZqSGwnEdcxM7tqZjtGazkJ4feMUX0Vy+mCZGyhCwLvO8qxsrhYlmRZ7FLGUxJ4Scg=="], @@ -2079,7 +2229,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.12", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.12.tgz", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/cacache": ["@types/cacache@20.0.1", "https://registry.npmmirror.com/@types/cacache/-/cacache-20.0.1.tgz", { "dependencies": { "@types/node": "*", "minipass": "*" } }, "sha512-QlKW3AFoFr/hvPHwFHMIVUH/ZCYeetBNou3PCmxu5LaNDvrtBlPJtIA6uhmU9JRt9oxj7IYoqoLcpxtzpPiTcw=="], @@ -2191,7 +2341,7 @@ "@types/mssql": ["@types/mssql@9.1.11", "https://registry.npmmirror.com/@types/mssql/-/mssql-9.1.11.tgz", { "dependencies": { "@types/node": "*", "tarn": "^3.0.1", "tedious": "*" } }, "sha512-vcujgrDbDezCxNDO4KY6gjwduLYOKfrexpRUwhoysRvcXZ3+IgZ/PMYFDgh8c3cQIxZ6skAwYo+H6ibMrBWPjQ=="], - "@types/node": ["@types/node@24.12.2", "https://registry.npmmirror.com/@types/node/-/node-24.12.2.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + "@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.13.tgz", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], @@ -2289,19 +2439,19 @@ "@vercel/oidc": ["@vercel/oidc@3.2.0", "https://registry.npmmirror.com/@vercel/oidc/-/oidc-3.2.0.tgz", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], - "@vitest/expect": ["@vitest/expect@3.2.4", "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + "@vitest/expect": ["@vitest/expect@4.1.0", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.0", "@vitest/utils": "4.1.0", "chai": "^6.2.2", "tinyrainbow": "^3.0.3" } }, "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA=="], - "@vitest/mocker": ["@vitest/mocker@3.2.4", "https://registry.npmmirror.com/@vitest/mocker/-/mocker-3.2.4.tgz", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], + "@vitest/mocker": ["@vitest/mocker@4.1.0", "", { "dependencies": { "@vitest/spy": "4.1.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw=="], - "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.0", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A=="], - "@vitest/runner": ["@vitest/runner@3.2.4", "https://registry.npmmirror.com/@vitest/runner/-/runner-3.2.4.tgz", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="], + "@vitest/runner": ["@vitest/runner@4.1.0", "", { "dependencies": { "@vitest/utils": "4.1.0", "pathe": "^2.0.3" } }, "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ=="], - "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="], + "@vitest/snapshot": ["@vitest/snapshot@4.1.0", "", { "dependencies": { "@vitest/pretty-format": "4.1.0", "@vitest/utils": "4.1.0", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg=="], - "@vitest/spy": ["@vitest/spy@3.2.4", "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "@vitest/spy": ["@vitest/spy@4.1.0", "", {}, "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw=="], - "@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + "@vitest/utils": ["@vitest/utils@4.1.0", "", { "dependencies": { "@vitest/pretty-format": "4.1.0", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw=="], "@vscode/codicons": ["@vscode/codicons@0.0.44", "https://registry.npmmirror.com/@vscode/codicons/-/codicons-0.0.44.tgz", {}, "sha512-F7qPRumUK3EHjNdopfICLGRf3iNPoZQt+McTHAn4AlOWPB3W2kL4H0S7uqEqbyZ6rCxaeDjpAn3MCUnwTu/VJQ=="], @@ -2465,7 +2615,7 @@ "aws-sdk": ["aws-sdk@2.1692.0", "https://registry.npmmirror.com/aws-sdk/-/aws-sdk-2.1692.0.tgz", { "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=="], - "aws4fetch": ["aws4fetch@1.0.18", "https://registry.npmmirror.com/aws4fetch/-/aws4fetch-1.0.18.tgz", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], + "aws4fetch": ["aws4fetch@1.0.20", "https://registry.npmmirror.com/aws4fetch/-/aws4fetch-1.0.20.tgz", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], "axe-core": ["axe-core@4.11.4", "https://registry.npmmirror.com/axe-core/-/axe-core-4.11.4.tgz", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], @@ -2551,7 +2701,7 @@ "bun-pty": ["bun-pty@0.4.8", "https://registry.npmmirror.com/bun-pty/-/bun-pty-0.4.8.tgz", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="], - "bun-types": ["bun-types@1.3.12", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.12.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "bundle-name": ["bundle-name@4.1.0", "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], @@ -2561,8 +2711,6 @@ "c8": ["c8@10.1.3", "https://registry.npmmirror.com/c8/-/c8-10.1.3.tgz", { "dependencies": { "@bcoe/v8-coverage": "^1.0.1", "@istanbuljs/schema": "^0.1.3", "find-up": "^5.0.0", "foreground-child": "^3.1.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", "test-exclude": "^7.0.1", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "monocart-coverage-reports": "^2" }, "optionalPeers": ["monocart-coverage-reports"], "bin": { "c8": "bin/c8.js" } }, "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA=="], - "cac": ["cac@6.7.14", "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - "cacache": ["cacache@20.0.4", "https://registry.npmmirror.com/cacache/-/cacache-20.0.4.tgz", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], "call-bind": ["call-bind@1.0.9", "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.9.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], @@ -2581,7 +2729,7 @@ "ccount": ["ccount@2.0.1", "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - "chai": ["chai@5.3.3", "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], "chainsaw": ["chainsaw@0.1.0", "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], @@ -2885,7 +3033,7 @@ "ee-first": ["ee-first@1.1.1", "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "effect": ["effect@4.0.0-beta.59", "https://registry.npmmirror.com/effect/-/effect-4.0.0-beta.59.tgz", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA=="], + "effect": ["effect@4.0.0-beta.65", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-QYKvQPAj3CmtsvWkHQww15wX4KG2gNsszDWEcOO5sZCMknp66u6Si/Opmt3wwWCwsyvRmDAdIg+JIz5qzbbFIw=="], "electron-to-chromium": ["electron-to-chromium@1.5.361", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", {}, "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA=="], @@ -2917,7 +3065,7 @@ "es-errors": ["es-errors@1.3.0", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-module-lexer": ["es-module-lexer@1.7.0", "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "es-module-lexer": ["es-module-lexer@2.3.0", "", {}, "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw=="], "es-object-atoms": ["es-object-atoms@1.1.2", "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], @@ -3121,6 +3269,8 @@ "get-tsconfig": ["get-tsconfig@4.14.0", "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.14.0.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="], + "giget": ["giget@2.0.0", "https://registry.npmmirror.com/giget/-/giget-2.0.0.tgz", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], "github-from-package": ["github-from-package@0.0.0", "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], @@ -3183,7 +3333,7 @@ "html-void-elements": ["html-void-elements@3.0.0", "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], - "htmlparser2": ["htmlparser2@10.1.0", "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-10.1.0.tgz", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], + "htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="], "http-cache-semantics": ["http-cache-semantics@4.2.0", "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], @@ -3685,6 +3835,8 @@ "object-inspect": ["object-inspect@1.13.4", "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + "obug": ["obug@2.1.3", "", {}, "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg=="], + "ohash": ["ohash@2.0.11", "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "oidc-token-hash": ["oidc-token-hash@5.2.0", "https://registry.npmmirror.com/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="], @@ -4169,7 +4321,7 @@ "statuses": ["statuses@2.0.2", "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], - "std-env": ["std-env@3.10.0", "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="], "stdin-discarder": ["stdin-discarder@0.2.2", "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], @@ -4203,8 +4355,6 @@ "strip-json-comments": ["strip-json-comments@5.0.3", "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-5.0.3.tgz", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], - "strip-literal": ["strip-literal@3.1.0", "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], - "strnum": ["strnum@2.3.0", "https://registry.npmmirror.com/strnum/-/strnum-2.3.0.tgz", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="], "structured-source": ["structured-source@4.0.0", "https://registry.npmmirror.com/structured-source/-/structured-source-4.0.0.tgz", { "dependencies": { "boundary": "^2.0.0" } }, "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA=="], @@ -4267,13 +4417,11 @@ "tinybench": ["tinybench@2.9.0", "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - "tinyexec": ["tinyexec@0.3.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyexec": ["tinyexec@1.2.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.2.2.tgz", {}, "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g=="], "tinyglobby": ["tinyglobby@0.2.16", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], - "tinypool": ["tinypool@1.1.1", "https://registry.npmmirror.com/tinypool/-/tinypool-1.1.1.tgz", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], - - "tinyrainbow": ["tinyrainbow@2.0.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], "tinyspy": ["tinyspy@4.0.4", "https://registry.npmmirror.com/tinyspy/-/tinyspy-4.0.4.tgz", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], @@ -4409,15 +4557,13 @@ "vite": ["vite@7.3.2", "https://registry.npmmirror.com/vite/-/vite-7.3.2.tgz", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], - "vite-node": ["vite-node@3.2.4", "https://registry.npmmirror.com/vite-node/-/vite-node-3.2.4.tgz", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], - "vite-plugin-icons-spritesheet": ["vite-plugin-icons-spritesheet@3.0.1", "https://registry.npmmirror.com/vite-plugin-icons-spritesheet/-/vite-plugin-icons-spritesheet-3.0.1.tgz", { "dependencies": { "chalk": "^5.4.1", "glob": "^11.0.1", "node-html-parser": "^7.0.1", "tinyexec": "^0.3.2" }, "peerDependencies": { "vite": ">=5.2.0" } }, "sha512-Cr0+Z6wRMwSwKisWW9PHeTjqmQFv0jwRQQMc3YgAhAgZEe03j21el0P/CA31KN/L5eiL1LhR14VTXl96LetonA=="], "vite-plugin-solid": ["vite-plugin-solid@2.11.10", "https://registry.npmmirror.com/vite-plugin-solid/-/vite-plugin-solid-2.11.10.tgz", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw=="], "vitefu": ["vitefu@1.1.3", "https://registry.npmmirror.com/vitefu/-/vitefu-1.1.3.tgz", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], - "vitest": ["vitest@3.2.4", "https://registry.npmmirror.com/vitest/-/vitest-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + "vitest": ["vitest@4.1.0", "", { "dependencies": { "@vitest/expect": "4.1.0", "@vitest/mocker": "4.1.0", "@vitest/pretty-format": "4.1.0", "@vitest/runner": "4.1.0", "@vitest/snapshot": "4.1.0", "@vitest/spy": "4.1.0", "@vitest/utils": "4.1.0", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.0", "@vitest/browser-preview": "4.1.0", "@vitest/browser-webdriverio": "4.1.0", "@vitest/ui": "4.1.0", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw=="], "vscode-jsonrpc": ["vscode-jsonrpc@8.2.1", "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="], @@ -4519,15 +4665,19 @@ "@actions/http-client/undici": ["undici@6.25.0", "https://registry.npmmirror.com/undici/-/undici-6.25.0.tgz", {}, "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg=="], - "@ai-sdk/amazon-bedrock/aws4fetch": ["aws4fetch@1.0.20", "https://registry.npmmirror.com/aws4fetch/-/aws4fetch-1.0.20.tgz", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], + "@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.41.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + + "@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.3.4", "https://registry.npmmirror.com/@smithy/eventstream-codec/-/eventstream-codec-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-uI00gSvwvBEiVuqpHFcMPGXnVh1XQYuqlQREIX/GUmuwNNTdGRliUI8R71tbfwNLYP7t8E/HVqdohvjoWU13iA=="], + + "@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.3.4", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-s8lfXcv+5C2GjBwGUBqFLgNmhyp9/n4TSKbOzKlIqJ/x0L/zwIxjNBC6DN4xUy59NvOrsiZI1t3tWi4ADUDyNw=="], "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-3.0.48.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="], "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.37.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], + "@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.10.tgz", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], "@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], @@ -4547,18 +4697,22 @@ "@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], - "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.48.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="], - "@ai-sdk/fireworks/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.10.tgz", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], "@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], "@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "https://registry.npmmirror.com/@ai-sdk/google/-/google-3.0.64.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="], + "@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.41.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.10.tgz", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], + + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + "@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -4571,9 +4725,11 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.6.0", "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.6.0.tgz", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.10.tgz", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - "@antfu/install-pkg/tinyexec": ["tinyexec@1.2.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.2.2.tgz", {}, "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g=="], + "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + + "@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.6.0", "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.6.0.tgz", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], @@ -4581,6 +4737,12 @@ "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/client-bedrock-runtime/@smithy/util-utf8": ["@smithy/util-utf8@4.3.4", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-s8lfXcv+5C2GjBwGUBqFLgNmhyp9/n4TSKbOzKlIqJ/x0L/zwIxjNBC6DN4xUy59NvOrsiZI1t3tWi4ADUDyNw=="], + + "@aws-sdk/client-cognito-identity/@smithy/util-utf8": ["@smithy/util-utf8@4.3.4", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-s8lfXcv+5C2GjBwGUBqFLgNmhyp9/n4TSKbOzKlIqJ/x0L/zwIxjNBC6DN4xUy59NvOrsiZI1t3tWi4ADUDyNw=="], + + "@aws-sdk/client-s3/@smithy/util-utf8": ["@smithy/util-utf8@4.3.4", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-s8lfXcv+5C2GjBwGUBqFLgNmhyp9/n4TSKbOzKlIqJ/x0L/zwIxjNBC6DN4xUy59NvOrsiZI1t3tWi4ADUDyNw=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.43", "https://registry.npmmirror.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.43.tgz", { "dependencies": { "@aws-sdk/core": "^3.974.13", "@aws-sdk/credential-provider-env": "^3.972.39", "@aws-sdk/credential-provider-http": "^3.972.41", "@aws-sdk/credential-provider-login": "^3.972.43", "@aws-sdk/credential-provider-process": "^3.972.39", "@aws-sdk/credential-provider-sso": "^3.972.43", "@aws-sdk/credential-provider-web-identity": "^3.972.43", "@aws-sdk/nested-clients": "^3.997.11", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.3", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-4mzII+3mZEVXXE1xzrLQrCJL7/r62A63bA6SVzZoNL5rqCJghpf+xgGltVrIBBs0n+mOZBKrQl2tRREtvZ5l6A=="], "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1052.0", "https://registry.npmmirror.com/@aws-sdk/token-providers/-/token-providers-3.1052.0.tgz", { "dependencies": { "@aws-sdk/core": "^3.974.13", "@aws-sdk/nested-clients": "^3.997.11", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-QqZNB3so7UIDxZtroc85TQaLVxdZRFm0eWM1CSR2N+b06as9TOrilvrlTZuj3guYlxMs6yLOgGxnklJ5qMYtTw=="], @@ -4633,10 +4795,6 @@ "@kilocode/kilo-docs/typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "@kilocode/kilo-gateway/@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-3.0.48.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="], - - "@kilocode/kilo-gateway/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.37.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], - "@kilocode/kilo-indexing/glob": ["glob@13.0.6", "https://registry.npmmirror.com/glob/-/glob-13.0.6.tgz", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "@manypkg/find-root/find-up": ["find-up@4.1.0", "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -4691,10 +4849,10 @@ "@openauthjs/openauth/@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.0.0-beta.3.tgz", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="], - "@openauthjs/openauth/aws4fetch": ["aws4fetch@1.0.20", "https://registry.npmmirror.com/aws4fetch/-/aws4fetch-1.0.20.tgz", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], - "@openauthjs/openauth/jose": ["jose@5.9.6", "https://registry.npmmirror.com/jose/-/jose-5.9.6.tgz", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], + "@opencode-ai/core/@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.993.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.993.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-cognito-identity": "^3.972.3", "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-login": "^3.972.9", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-1M/nukgPSLqe9krzOKHnE8OylUaKAiokAV3xRLdeExVHcRE7WG5uzCTKWTj1imKvPjDqXq/FWhlbbdWIn7xIwA=="], + "@opencode-ai/shared/semver": ["semver@7.7.4", "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "@opencode-ai/storybook/@storybook/addon-a11y": ["@storybook/addon-a11y@10.4.1", "https://registry.npmmirror.com/@storybook/addon-a11y/-/addon-a11y-10.4.1.tgz", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.4.1" } }, "sha512-MGft/IXjJ20a9KbaSVG9bHTAAoanbucKrgEiJJRNqpim8DsXA01+XTdSk17LmiOCB203Rrq9mWgdQ6+79cc8iA=="], @@ -4743,6 +4901,10 @@ "@shikijs/themes/@shikijs/types": ["@shikijs/types@3.20.0", "https://registry.npmmirror.com/@shikijs/types/-/types-3.20.0.tgz", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + "@smithy/util-buffer-from/@smithy/core": ["@smithy/core@3.29.3", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A=="], + + "@smithy/util-hex-encoding/@smithy/core": ["@smithy/core@3.29.3", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A=="], + "@solid-primitives/resize-observer/@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "https://registry.npmmirror.com/@solid-primitives/rootless/-/rootless-1.5.3.tgz", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="], "@storybook/addon-links/storybook": ["storybook@10.4.1", "https://registry.npmmirror.com/storybook/-/storybook-10.4.1.tgz", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-V1Zd2e+gBFufqAQVZ1JR8KLqALsEZ3JYSBnWwQbKa6zCfWWanR6AFMyuOkLt2gZOgGp3h2Riuz88pGNVTQSG0A=="], @@ -4787,6 +4949,8 @@ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@vscode/test-cli/chokidar": ["chokidar@3.6.0", "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "@vscode/test-cli/glob": ["glob@10.5.0", "https://registry.npmmirror.com/glob/-/glob-10.5.0.tgz", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], @@ -4807,8 +4971,14 @@ "@vscode/vsce/minimatch": ["minimatch@3.1.5", "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "ai-gateway-provider/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.41", "https://registry.npmmirror.com/@ai-sdk/cerebras/-/cerebras-2.0.41.tgz", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kDMEpjaRdRXIUi1EH8WHwLRahyDTYv9SAJnP6VCCeq8X+tVqZbMLCqqxSG5dRknrI65ucjvzQt+FiDKTAa7AHg=="], + "ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "https://registry.npmmirror.com/@ai-sdk/google/-/google-3.0.64.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="], + "ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "https://registry.npmmirror.com/@ai-sdk/xai/-/xai-3.0.82.tgz", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + + "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "https://registry.npmmirror.com/@openrouter/ai-sdk-provider/-/ai-sdk-provider-2.8.1.tgz", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="], + "archiver-utils/glob": ["glob@10.5.0", "https://registry.npmmirror.com/glob/-/glob-10.5.0.tgz", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "archiver-utils/is-stream": ["is-stream@2.0.1", "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], @@ -4853,6 +5023,8 @@ "chalk-template/chalk": ["chalk@4.1.2", "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "cheerio/htmlparser2": ["htmlparser2@10.1.0", "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-10.1.0.tgz", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], + "cheerio/undici": ["undici@7.25.0", "https://registry.npmmirror.com/undici/-/undici-7.25.0.tgz", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], "clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -4927,6 +5099,8 @@ "gray-matter/js-yaml": ["js-yaml@3.14.2", "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + "htmlparser2/entities": ["entities@4.5.0", "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "import-fresh/resolve-from": ["resolve-from@4.0.0", "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "isomorphic-git/ignore": ["ignore@5.3.2", "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -5005,8 +5179,6 @@ "nypm/citty": ["citty@0.2.2", "https://registry.npmmirror.com/citty/-/citty-0.2.2.tgz", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], - "nypm/tinyexec": ["tinyexec@1.2.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.2.2.tgz", {}, "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g=="], - "opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.6.1.tgz", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="], "opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "https://registry.npmmirror.com/@tsconfig/bun/-/bun-1.0.7.tgz", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="], @@ -5085,8 +5257,14 @@ "spdx-correct/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + "sst/aws4fetch": ["aws4fetch@1.0.18", "https://registry.npmmirror.com/aws4fetch/-/aws4fetch-1.0.18.tgz", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], + "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + "storybook/@vitest/expect": ["@vitest/expect@3.2.4", "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "storybook/@vitest/spy": ["@vitest/spy@3.2.4", "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "storybook/open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -5095,8 +5273,6 @@ "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "strip-literal/js-tokens": ["js-tokens@9.0.1", "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], - "supertap/js-yaml": ["js-yaml@3.14.2", "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], "supports-hyperlinks/supports-color": ["supports-color@7.2.0", "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -5131,8 +5307,12 @@ "validate-npm-package-license/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + "venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.41.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "https://registry.npmmirror.com/glob/-/glob-11.1.0.tgz", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], + "vite-plugin-icons-spritesheet/tinyexec": ["tinyexec@0.3.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "vitest/why-is-node-running": ["why-is-node-running@2.3.0", "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -5175,12 +5355,22 @@ "@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/perplexity/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@ai-sdk/togetherai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.14", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.14.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], @@ -5191,10 +5381,6 @@ "@hey-api/openapi-ts/open/wsl-utils": ["wsl-utils@0.3.1", "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.3.1.tgz", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], - "@kilocode/kilo-gateway/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - - "@kilocode/kilo-gateway/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "@morphllm/morphsdk/openai/zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -5233,12 +5419,22 @@ "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "https://registry.npmmirror.com/before-after-hook/-/before-after-hook-4.0.0.tgz", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7Ne3Yk/bgQPVebAkv7W+RfhiwTRSbfER9BtbhOa2w/+dIr902LrJf6vrZlxiqaJbGj2ALx8M+ZK1YIHVxSwu9A=="], + + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.43", "https://registry.npmmirror.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.43.tgz", { "dependencies": { "@aws-sdk/core": "^3.974.13", "@aws-sdk/credential-provider-env": "^3.972.39", "@aws-sdk/credential-provider-http": "^3.972.41", "@aws-sdk/credential-provider-login": "^3.972.43", "@aws-sdk/credential-provider-process": "^3.972.39", "@aws-sdk/credential-provider-sso": "^3.972.43", "@aws-sdk/credential-provider-web-identity": "^3.972.43", "@aws-sdk/nested-clients": "^3.997.11", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.3", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-4mzII+3mZEVXXE1xzrLQrCJL7/r62A63bA6SVzZoNL5rqCJghpf+xgGltVrIBBs0n+mOZBKrQl2tRREtvZ5l6A=="], + + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ=="], + "@opencode-ai/storybook/@storybook/addon-docs/@storybook/csf-plugin": ["@storybook/csf-plugin@10.4.1", "https://registry.npmmirror.com/@storybook/csf-plugin/-/csf-plugin-10.4.1.tgz", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.4.1", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-WdPepGBxDGOUDjYd8KxMtcf+us/2PAcnBczl77XtrnxxHNs0jWesxKkiJ9yiuGrge4BPhDeAj6rxjbBoaHxLBA=="], "@opencode-ai/storybook/@storybook/addon-docs/@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.4.1", "https://registry.npmmirror.com/@storybook/react-dom-shim/-/react-dom-shim-10.4.1.tgz", { "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.4.1" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-6QFqfDNH4DMrt7yHKRfpqRopsVUc/Az+sXIdJ39IetYnHUxL3nW4NVaPc6uy/8Qi8urzUyEXL/nn7cpSIP2aPQ=="], "@opencode-ai/storybook/@storybook/addon-docs/react": ["react@19.2.6", "https://registry.npmmirror.com/react/-/react-19.2.6.tgz", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="], + "@opencode-ai/storybook/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@opencode-ai/storybook/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "@opencode-ai/storybook/storybook/open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "@opencode-ai/ui/@solid-primitives/resize-observer/@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "https://registry.npmmirror.com/@solid-primitives/rootless/-/rootless-1.5.3.tgz", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="], @@ -5251,12 +5447,32 @@ "@opentui/solid/@babel/core/semver": ["semver@6.3.1", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@smithy/util-buffer-from/@smithy/core/@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="], + + "@smithy/util-hex-encoding/@smithy/core/@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="], + + "@storybook/addon-links/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@storybook/addon-links/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "@storybook/addon-links/storybook/open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "@storybook/addon-onboarding/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@storybook/addon-onboarding/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "@storybook/addon-onboarding/storybook/open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "@storybook/addon-vitest/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@storybook/addon-vitest/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "@storybook/addon-vitest/storybook/open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "@storybook/builder-vite/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@storybook/builder-vite/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "@storybook/builder-vite/storybook/open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "@tailwindcss/vite/@tailwindcss/node/lightningcss": ["lightningcss@1.30.1", "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.1.tgz", { "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=="], @@ -5317,6 +5533,12 @@ "@vscode/vsce/minimatch/brace-expansion": ["brace-expansion@1.1.14", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.14.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.37.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], + + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "ai-gateway-provider/@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.41.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "archiver-utils/glob/minimatch": ["minimatch@9.0.9", "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -5515,6 +5737,12 @@ "secretlint/globby/slash": ["slash@5.1.0", "https://registry.npmmirror.com/slash/-/slash-5.1.0.tgz", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], + "storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "storybook/@vitest/expect/chai": ["chai@5.3.3", "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "table/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -5541,10 +5769,6 @@ "@eslint/eslintrc/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@kilocode/kilo-gateway/@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@kilocode/kilo-gateway/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@manypkg/find-root/find-up/locate-path/p-locate": ["p-locate@4.1.0", "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "@octokit/graphql/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "https://registry.npmmirror.com/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -5555,6 +5779,46 @@ "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "https://registry.npmmirror.com/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity/@smithy/util-utf8": ["@smithy/util-utf8@4.3.4", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-s8lfXcv+5C2GjBwGUBqFLgNmhyp9/n4TSKbOzKlIqJ/x0L/zwIxjNBC6DN4xUy59NvOrsiZI1t3tWi4ADUDyNw=="], + + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.11", "https://registry.npmmirror.com/@aws-sdk/nested-clients/-/nested-clients-3.997.11.tgz", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.13", "@aws-sdk/signature-v4-multi-region": "^3.996.28", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.3", "@smithy/fetch-http-handler": "^5.4.3", "@smithy/node-http-handler": "^4.7.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-nWXXJ1r/r8N2Gw1pWolRgED38/A9A8DHR2ETWIv220zh4PZHcybbR4hUVWWktmNXTRHzDJwRluapHn0rZxuoqA=="], + + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.3.4", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.3.4.tgz", { "dependencies": { "@smithy/core": "^3.24.4", "tslib": "^2.6.2" } }, "sha512-s8lfXcv+5C2GjBwGUBqFLgNmhyp9/n4TSKbOzKlIqJ/x0L/zwIxjNBC6DN4xUy59NvOrsiZI1t3tWi4ADUDyNw=="], + + "@opencode-ai/storybook/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@opencode-ai/storybook/storybook/@vitest/expect/chai": ["chai@5.3.3", "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@opencode-ai/storybook/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "@storybook/addon-links/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@storybook/addon-links/storybook/@vitest/expect/chai": ["chai@5.3.3", "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@storybook/addon-links/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "@storybook/addon-onboarding/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@storybook/addon-onboarding/storybook/@vitest/expect/chai": ["chai@5.3.3", "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@storybook/addon-onboarding/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "@storybook/addon-vitest/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@storybook/addon-vitest/storybook/@vitest/expect/chai": ["chai@5.3.3", "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@storybook/addon-vitest/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "@storybook/builder-vite/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@storybook/builder-vite/storybook/@vitest/expect/chai": ["chai@5.3.3", "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@storybook/builder-vite/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "@tailwindcss/vite/@tailwindcss/node/lightningcss/detect-libc": ["detect-libc@2.1.2", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "@tailwindcss/vite/@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], @@ -5605,6 +5869,8 @@ "@vscode/vsce/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "archiver-utils/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.0.tgz", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], @@ -5685,6 +5951,8 @@ "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + "tar-fs/tar-stream/bl/buffer": ["buffer@5.7.1", "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "test-exclude/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], @@ -5695,6 +5963,16 @@ "@manypkg/find-root/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "@opencode-ai/storybook/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@storybook/addon-links/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@storybook/addon-onboarding/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@storybook/addon-vitest/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@storybook/builder-vite/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + "@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], "@vscode/test-cli/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], diff --git a/package.json b/package.json index 0775c0fbc2..92c3c8dbed 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "@types/cross-spawn": "6.0.6", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", - "@opentui/core": "0.2.10", - "@opentui/solid": "0.2.10", + "@opentui/core": "0.2.11", + "@opentui/solid": "0.2.11", "ulid": "3.0.1", "@kobalte/core": "0.13.11", "@types/luxon": "3.7.1", @@ -78,7 +78,7 @@ "solid-js": "1.9.12", "vite-plugin-solid": "2.11.10", "@lydell/node-pty": "1.2.0-beta.10", - "@opentui/keymap": "0.2.10" + "@opentui/keymap": "0.2.11" } }, "devDependencies": { diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts index 70cdba7856..d27f17bfb8 100644 --- a/packages/core/src/catalog.ts +++ b/packages/core/src/catalog.ts @@ -5,7 +5,8 @@ import { produce, type Draft } from "immer" import { ModelV2 } from "./model" import { PluginV2 } from "./plugin" import { ProviderV2 } from "./provider" -import { Instance } from "./instance" +import { Location } from "./location" +import { EventV2 } from "./event" type ProviderRecord = { provider: ProviderV2.Info @@ -24,6 +25,15 @@ export class ModelNotFoundError extends Schema.TaggedErrorClass Effect.Effect @@ -57,10 +67,11 @@ export class Service extends Context.Service()("@opencode/v2 export const layer = Layer.effect( Service, Effect.gen(function* () { - yield* Instance.Service + yield* Location.Service let records = HashMap.empty() let defaultModel: { providerID: ProviderV2.ID; modelID: ModelV2.ID } | undefined const plugin = yield* PluginV2.Service + const events = yield* EventV2.Service const resolve = (model: ModelV2.Info) => { const provider = Option.getOrThrow(HashMap.get(records, model.providerID)).provider @@ -157,14 +168,12 @@ export const layer = Layer.effect( ) const updated = yield* plugin.trigger("model.update", {}, { model, cancel: false }) if (updated.cancel) return + const next = new ModelV2.Info({ ...updated.model, id: modelID, providerID }) records = HashMap.set(records, providerID, { provider: record.provider, - models: HashMap.set( - record.models, - modelID, - new ModelV2.Info({ ...updated.model, id: modelID, providerID }), - ), + models: HashMap.set(record.models, modelID, next), }) + yield* events.publish(Event.ModelUpdated, { model: resolve(next) }) return }), @@ -257,4 +266,4 @@ export const layer = Layer.effect( const SMALL_MODEL_RE = /\b(nano|flash|lite|mini|haiku|small|fast)\b/ -export const defaultLayer = layer.pipe(Layer.provide(PluginV2.defaultLayer)) +export const defaultLayer = layer.pipe(Layer.provideMerge(EventV2.defaultLayer), Layer.provide(PluginV2.defaultLayer)) diff --git a/packages/core/src/event.ts b/packages/core/src/event.ts new file mode 100644 index 0000000000..e01dc5b0d6 --- /dev/null +++ b/packages/core/src/event.ts @@ -0,0 +1,157 @@ +import { Context, Effect, Layer, Option, PubSub, Schema, Stream } from "effect" +import { Location } from "./location" +import { withStatics } from "./schema" +import { Identifier } from "./util/identifier" + +export const ID = Schema.String.pipe( + Schema.brand("Event.ID"), + withStatics((schema) => ({ create: () => schema.make("evt_" + Identifier.ascending()) })), +) +export type ID = typeof ID.Type + +export type Definition = { + readonly type: Type + readonly version?: number + readonly aggregate?: string + readonly data: DataSchema +} + +export type Data = Schema.Schema.Type + +export type Payload = { + readonly id: ID + readonly type: D["type"] + readonly data: Data + readonly version?: number + readonly location?: Location.Ref + readonly metadata?: Record +} + +export type Sync = (event: Payload) => Effect.Effect + +export const registry = new Map() + +export function define(input: { + readonly type: Type + readonly version?: number + readonly aggregate?: string + readonly schema: Fields +}): Schema.Schema>>> & Definition> { + const Data = Schema.Struct(input.schema) + const Payload = Schema.Struct({ + id: ID, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + type: Schema.Literal(input.type), + version: Schema.optional(Schema.Number), + location: Schema.optional(Location.Ref), + data: Data, + }).annotate({ identifier: input.type }) + + const definition = Object.assign(Payload, { + type: input.type, + ...(input.version === undefined ? {} : { version: input.version }), + ...(input.aggregate === undefined ? {} : { aggregate: input.aggregate }), + data: Data, + }) + registry.set(input.type, definition) + return definition as Schema.Schema>>> & + Definition> +} + +export function definitions() { + return registry.values().toArray() +} + +export interface PublishOptions { + readonly id?: ID + readonly metadata?: Record +} + +export type Unsubscribe = Effect.Effect + +export interface Interface { + readonly publish: ( + definition: D, + data: Data, + options?: PublishOptions, + ) => Effect.Effect> + readonly publishEvent: (event: Payload) => Effect.Effect> + readonly subscribe: (definition: D) => Stream.Stream> + readonly all: () => Stream.Stream + readonly sync: (handler: Sync) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/Event") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const all = yield* PubSub.unbounded() + const typed = new Map>() + const syncHandlers = new Array() + + const getOrCreate = (definition: Definition) => + Effect.gen(function* () { + const existing = typed.get(definition.type) + if (existing) return existing + const pubsub = yield* PubSub.unbounded() + typed.set(definition.type, pubsub) + return pubsub + }) + + yield* Effect.addFinalizer(() => + Effect.gen(function* () { + yield* PubSub.shutdown(all) + yield* Effect.forEach(typed.values(), PubSub.shutdown, { discard: true }) + }), + ) + + function publishEvent(event: Payload) { + return Effect.gen(function* () { + for (const sync of syncHandlers) { + yield* sync(event as Payload) + } + const pubsub = typed.get(event.type) + if (pubsub) yield* PubSub.publish(pubsub, event as Payload) + yield* PubSub.publish(all, event as Payload) + return event + }) + } + + function publish(definition: D, data: Data, options?: PublishOptions) { + return Effect.gen(function* () { + const location = Option.getOrUndefined(yield* Effect.serviceOption(Location.Service)) + const event = { + id: options?.id ?? ID.create(), + ...(options?.metadata ? { metadata: options.metadata } : {}), + type: definition.type, + ...(definition.version === undefined ? {} : { version: definition.version }), + ...(location ? { location } : {}), + data, + } as Payload + return yield* publishEvent(event) + }) + } + + const subscribe = (definition: D): Stream.Stream> => + Stream.unwrap(getOrCreate(definition).pipe(Effect.map((pubsub) => Stream.fromPubSub(pubsub)))).pipe( + Stream.map((event) => event as Payload), + ) + + const streamAll = (): Stream.Stream => Stream.fromPubSub(all) + const sync = (handler: Sync): Effect.Effect => + Effect.sync(() => { + syncHandlers.push(handler) + return Effect.sync(() => { + const index = syncHandlers.indexOf(handler) + if (index >= 0) syncHandlers.splice(index, 1) + }) + }) + + return Service.of({ publish, publishEvent, subscribe, all: streamAll, sync }) + }), +) + +export const defaultLayer = layer + +export * as EventV2 from "./event" diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index ab0647c8af..0ed6eb4770 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -53,7 +53,7 @@ export const Flag = { KILO_DISABLE_CLAUDE_CODE_PROMPT: KILO_DISABLE_CLAUDE_CODE || truthy("KILO_DISABLE_CLAUDE_CODE_PROMPT"), KILO_DISABLE_CLAUDE_CODE_SKILLS, KILO_DISABLE_EXTERNAL_SKILLS: truthy("KILO_DISABLE_EXTERNAL_SKILLS"), - KILO_EXPERIMENTAL_CUSTOMIZE_SKILL: unstableDefault("KILO_EXPERIMENTAL_CUSTOMIZE_SKILL"), + KILO_EXPERIMENTAL_CUSTOMIZE_SKILL: unstableDefault("KILO_EXPERIMENTAL_CUSTOMIZE_SKILL"), // kilocode_change KILO_FAKE_VCS: process.env["KILO_FAKE_VCS"], KILO_SERVER_PASSWORD: process.env["KILO_SERVER_PASSWORD"], KILO_SERVER_USERNAME: process.env["KILO_SERVER_USERNAME"], diff --git a/packages/core/src/location-layer.ts b/packages/core/src/location-layer.ts new file mode 100644 index 0000000000..84dfb3dfae --- /dev/null +++ b/packages/core/src/location-layer.ts @@ -0,0 +1,12 @@ +import { Layer, LayerMap } from "effect" +import { Location } from "./location" +import { Catalog } from "./catalog" +import { PluginBoot } from "./plugin/boot" + +export class LocationServiceMap extends LayerMap.Service()("@opencode/example/LocationServiceMap", { + lookup: (ref: Location.Ref) => { + const location = Layer.succeed(Location.Service, Location.Service.of(ref)) + return Layer.mergeAll(Catalog.defaultLayer, PluginBoot.defaultLayer).pipe(Layer.provide(location)) + }, + idleTimeToLive: "5 minutes", +}) {} diff --git a/packages/core/src/location.ts b/packages/core/src/location.ts new file mode 100644 index 0000000000..00ff9cd3ea --- /dev/null +++ b/packages/core/src/location.ts @@ -0,0 +1,11 @@ +import { Context, Schema } from "effect" + +export * as Location from "./location" + +export const Ref = Schema.Struct({ + directory: Schema.String, + workspaceID: Schema.optional(Schema.String), +}).annotate({ identifier: "Location.Ref" }) +export type Ref = typeof Ref.Type + +export class Service extends Context.Service()("@opencode/Location") {} diff --git a/packages/opencode/src/v2/session-event.ts b/packages/core/src/session-event.ts similarity index 71% rename from packages/opencode/src/v2/session-event.ts rename to packages/core/src/session-event.ts index 1fd0f909d5..a98d9cc051 100644 --- a/packages/opencode/src/v2/session-event.ts +++ b/packages/core/src/session-event.ts @@ -1,12 +1,13 @@ -import { SessionID } from "@/session/schema" -import { NonNegativeInt } from "@opencode-ai/core/schema" -import { EventV2 } from "./event" -import { FileAttachment, Prompt } from "@opencode-ai/core/session-prompt" import { Schema } from "effect" +import { EventV2 } from "./event" +import { ModelV2 } from "./model" +import { NonNegativeInt } from "./schema" +import { Session } from "./session" +import { FileAttachment, Prompt } from "./session-prompt" +import { ToolOutput } from "./tool-output" +import { V2Schema } from "./v2-schema" + export { FileAttachment } -import { ToolOutput } from "@opencode-ai/core/tool-output" -import { V2Schema } from "@opencode-ai/core/v2-schema" -import { ModelV2 } from "@opencode-ai/core/model" export const Source = Schema.Struct({ start: NonNegativeInt, @@ -15,92 +16,94 @@ export const Source = Schema.Struct({ }).annotate({ identifier: "session.next.event.source", }) -export type Source = Schema.Schema.Type +export type Source = typeof Source.Type const Base = { timestamp: V2Schema.DateTimeUtcFromMillis, - sessionID: SessionID, + sessionID: Session.ID, } +const options = { + aggregate: "sessionID", + version: 1, +} as const + export const UnknownError = Schema.Struct({ type: Schema.Literal("unknown"), message: Schema.String, }).annotate({ identifier: "Session.Error.Unknown", }) -export type UnknownError = Schema.Schema.Type +export type UnknownError = typeof UnknownError.Type export const AgentSwitched = EventV2.define({ type: "session.next.agent.switched", - aggregate: "sessionID", - version: 1, + ...options, schema: { ...Base, agent: Schema.String, }, }) -export type AgentSwitched = Schema.Schema.Type +export type AgentSwitched = typeof AgentSwitched.Type export const ModelSwitched = EventV2.define({ type: "session.next.model.switched", - aggregate: "sessionID", - version: 1, + ...options, schema: { ...Base, model: ModelV2.Ref, }, }) -export type ModelSwitched = Schema.Schema.Type +export type ModelSwitched = typeof ModelSwitched.Type export const Prompted = EventV2.define({ type: "session.next.prompted", - aggregate: "sessionID", - version: 1, + ...options, schema: { ...Base, prompt: Prompt, }, }) -export type Prompted = Schema.Schema.Type +export type Prompted = typeof Prompted.Type export const Synthetic = EventV2.define({ type: "session.next.synthetic", - aggregate: "sessionID", + ...options, schema: { ...Base, text: Schema.String, }, }) -export type Synthetic = Schema.Schema.Type +export type Synthetic = typeof Synthetic.Type export namespace Shell { export const Started = EventV2.define({ type: "session.next.shell.started", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, command: Schema.String, }, }) - export type Started = Schema.Schema.Type + export type Started = typeof Started.Type export const Ended = EventV2.define({ type: "session.next.shell.ended", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, output: Schema.String, }, }) - export type Ended = Schema.Schema.Type + export type Ended = typeof Ended.Type } export namespace Step { export const Started = EventV2.define({ type: "session.next.step.started", - aggregate: "sessionID", + ...options, schema: { ...Base, agent: Schema.String, @@ -108,11 +111,11 @@ export namespace Step { snapshot: Schema.String.pipe(Schema.optional), }, }) - export type Started = Schema.Schema.Type + export type Started = typeof Started.Type export const Ended = EventV2.define({ type: "session.next.step.ended", - aggregate: "sessionID", + ...options, schema: { ...Base, finish: Schema.String, @@ -129,123 +132,123 @@ export namespace Step { snapshot: Schema.String.pipe(Schema.optional), }, }) - export type Ended = Schema.Schema.Type + export type Ended = typeof Ended.Type export const Failed = EventV2.define({ type: "session.next.step.failed", - aggregate: "sessionID", + ...options, schema: { ...Base, error: UnknownError, }, }) - export type Failed = Schema.Schema.Type + export type Failed = typeof Failed.Type } export namespace Text { export const Started = EventV2.define({ type: "session.next.text.started", - aggregate: "sessionID", + ...options, schema: { ...Base, }, }) - export type Started = Schema.Schema.Type + export type Started = typeof Started.Type export const Delta = EventV2.define({ type: "session.next.text.delta", - aggregate: "sessionID", + ...options, schema: { ...Base, delta: Schema.String, }, }) - export type Delta = Schema.Schema.Type + export type Delta = typeof Delta.Type export const Ended = EventV2.define({ type: "session.next.text.ended", - aggregate: "sessionID", + ...options, schema: { ...Base, text: Schema.String, }, }) - export type Ended = Schema.Schema.Type + export type Ended = typeof Ended.Type } export namespace Reasoning { export const Started = EventV2.define({ type: "session.next.reasoning.started", - aggregate: "sessionID", + ...options, schema: { ...Base, reasoningID: Schema.String, }, }) - export type Started = Schema.Schema.Type + export type Started = typeof Started.Type export const Delta = EventV2.define({ type: "session.next.reasoning.delta", - aggregate: "sessionID", + ...options, schema: { ...Base, reasoningID: Schema.String, delta: Schema.String, }, }) - export type Delta = Schema.Schema.Type + export type Delta = typeof Delta.Type export const Ended = EventV2.define({ type: "session.next.reasoning.ended", - aggregate: "sessionID", + ...options, schema: { ...Base, reasoningID: Schema.String, text: Schema.String, }, }) - export type Ended = Schema.Schema.Type + export type Ended = typeof Ended.Type } export namespace Tool { export namespace Input { export const Started = EventV2.define({ type: "session.next.tool.input.started", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, name: Schema.String, }, }) - export type Started = Schema.Schema.Type + export type Started = typeof Started.Type export const Delta = EventV2.define({ type: "session.next.tool.input.delta", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, delta: Schema.String, }, }) - export type Delta = Schema.Schema.Type + export type Delta = typeof Delta.Type export const Ended = EventV2.define({ type: "session.next.tool.input.ended", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, text: Schema.String, }, }) - export type Ended = Schema.Schema.Type + export type Ended = typeof Ended.Type } export const Called = EventV2.define({ type: "session.next.tool.called", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, @@ -257,11 +260,11 @@ export namespace Tool { }), }, }) - export type Called = Schema.Schema.Type + export type Called = typeof Called.Type export const Progress = EventV2.define({ type: "session.next.tool.progress", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, @@ -269,11 +272,11 @@ export namespace Tool { content: Schema.Array(ToolOutput.Content), }, }) - export type Progress = Schema.Schema.Type + export type Progress = typeof Progress.Type export const Success = EventV2.define({ type: "session.next.tool.success", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, @@ -285,11 +288,11 @@ export namespace Tool { }), }, }) - export type Success = Schema.Schema.Type + export type Success = typeof Success.Type export const Failed = EventV2.define({ type: "session.next.tool.failed", - aggregate: "sessionID", + ...options, schema: { ...Base, callID: Schema.String, @@ -300,7 +303,7 @@ export namespace Tool { }), }, }) - export type Failed = Schema.Schema.Type + export type Failed = typeof Failed.Type } export const RetryError = Schema.Struct({ @@ -313,49 +316,50 @@ export const RetryError = Schema.Struct({ }).annotate({ identifier: "session.next.retry_error", }) -export type RetryError = Schema.Schema.Type +export type RetryError = typeof RetryError.Type export const Retried = EventV2.define({ type: "session.next.retried", - aggregate: "sessionID", + ...options, schema: { ...Base, attempt: Schema.Finite, error: RetryError, }, }) -export type Retried = Schema.Schema.Type +export type Retried = typeof Retried.Type export namespace Compaction { export const Started = EventV2.define({ type: "session.next.compaction.started", - aggregate: "sessionID", + ...options, schema: { ...Base, reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")]), }, }) - export type Started = Schema.Schema.Type + export type Started = typeof Started.Type export const Delta = EventV2.define({ type: "session.next.compaction.delta", - aggregate: "sessionID", + ...options, schema: { ...Base, text: Schema.String, }, }) + export type Delta = typeof Delta.Type export const Ended = EventV2.define({ type: "session.next.compaction.ended", - aggregate: "sessionID", + ...options, schema: { ...Base, text: Schema.String, include: Schema.String.pipe(Schema.optional), }, }) - export type Ended = Schema.Schema.Type + export type Ended = typeof Ended.Type } export const All = Schema.Union( @@ -392,16 +396,7 @@ export const All = Schema.Union( }, ).pipe(Schema.toTaggedUnion("type")) -// user -// assistant -// assistant -// assistant -// user -// compaction marker -// -> text -// assistant - -export type Event = Schema.Schema.Type +export type Event = typeof All.Type export type Type = Event["type"] export * as SessionEvent from "./session-event" diff --git a/packages/opencode/src/v2/session-message-updater.ts b/packages/core/src/session-message-updater.ts similarity index 100% rename from packages/opencode/src/v2/session-message-updater.ts rename to packages/core/src/session-message-updater.ts diff --git a/packages/opencode/src/v2/session-message.ts b/packages/core/src/session-message.ts similarity index 87% rename from packages/opencode/src/v2/session-message.ts rename to packages/core/src/session-message.ts index fa7c299ae5..73b6dd7da2 100644 --- a/packages/opencode/src/v2/session-message.ts +++ b/packages/core/src/session-message.ts @@ -1,10 +1,10 @@ import { Schema } from "effect" -import { Prompt } from "@opencode-ai/core/session-prompt" +import { Prompt } from "./session-prompt" import { SessionEvent } from "./session-event" import { EventV2 } from "./event" -import { ToolOutput } from "@opencode-ai/core/tool-output" -import { V2Schema } from "@opencode-ai/core/v2-schema" -import { ModelV2 } from "@opencode-ai/core/model" +import { ToolOutput } from "./tool-output" +import { V2Schema } from "./v2-schema" +import { ModelV2 } from "./model" export const ID = EventV2.ID export type ID = Schema.Schema.Type @@ -20,7 +20,7 @@ const Base = { export class AgentSwitched extends Schema.Class("Session.Message.AgentSwitched")({ ...Base, type: Schema.Literal("agent-switched"), - agent: SessionEvent.AgentSwitched.fields.data.fields.agent, + agent: SessionEvent.AgentSwitched.data.fields.agent, }) {} export class ModelSwitched extends Schema.Class("Session.Message.ModelSwitched")({ @@ -43,16 +43,16 @@ export class User extends Schema.Class("Session.Message.User")({ export class Synthetic extends Schema.Class("Session.Message.Synthetic")({ ...Base, - sessionID: SessionEvent.Synthetic.fields.data.fields.sessionID, - text: SessionEvent.Synthetic.fields.data.fields.text, + sessionID: SessionEvent.Synthetic.data.fields.sessionID, + text: SessionEvent.Synthetic.data.fields.text, type: Schema.Literal("synthetic"), }) {} export class Shell extends Schema.Class("Session.Message.Shell")({ ...Base, type: Schema.Literal("shell"), - callID: SessionEvent.Shell.Started.fields.data.fields.callID, - command: SessionEvent.Shell.Started.fields.data.fields.command, + callID: SessionEvent.Shell.Started.data.fields.callID, + command: SessionEvent.Shell.Started.data.fields.command, output: Schema.String, time: Schema.Struct({ created: V2Schema.DateTimeUtcFromMillis, @@ -130,7 +130,7 @@ export class Assistant extends Schema.Class("Session.Message.Assistan ...Base, type: Schema.Literal("assistant"), agent: Schema.String, - model: SessionEvent.Step.Started.fields.data.fields.model, + model: SessionEvent.Step.Started.data.fields.model, content: AssistantContent.pipe(Schema.Array), snapshot: Schema.Struct({ start: Schema.String.pipe(Schema.optional), @@ -147,7 +147,7 @@ export class Assistant extends Schema.Class("Session.Message.Assistan write: Schema.Finite, }), }).pipe(Schema.optional), - error: SessionEvent.Step.Failed.fields.data.fields.error.pipe(Schema.optional), + error: SessionEvent.Step.Failed.data.fields.error.pipe(Schema.optional), time: Schema.Struct({ created: V2Schema.DateTimeUtcFromMillis, completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional), @@ -156,7 +156,7 @@ export class Assistant extends Schema.Class("Session.Message.Assistan export class Compaction extends Schema.Class("Session.Message.Compaction")({ type: Schema.Literal("compaction"), - reason: SessionEvent.Compaction.Started.fields.data.fields.reason, + reason: SessionEvent.Compaction.Started.data.fields.reason, summary: Schema.String, include: Schema.String.pipe(Schema.optional), ...Base, diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts new file mode 100644 index 0000000000..756531e328 --- /dev/null +++ b/packages/core/src/session.ts @@ -0,0 +1,13 @@ +export * as Session from "./session" + +import { Schema } from "effect" +import { withStatics } from "./schema" +import { Identifier } from "./util/identifier" + +export const ID = Schema.String.check(Schema.isStartsWith("ses")).pipe( + Schema.brand("SessionID"), + withStatics((schema) => ({ + descending: (id?: string) => schema.make(id ?? "ses_" + Identifier.descending()), + })), +) +export type ID = typeof ID.Type diff --git a/packages/core/test/catalog.test.ts b/packages/core/test/catalog.test.ts index c5ac2c06cc..594f42d1c8 100644 --- a/packages/core/test/catalog.test.ts +++ b/packages/core/test/catalog.test.ts @@ -1,14 +1,21 @@ import { describe, expect } from "bun:test" -import { DateTime, Effect, Layer, Option } from "effect" +import { DateTime, Effect, Fiber, Layer, Option, Stream } from "effect" import { Catalog } from "@opencode-ai/core/catalog" -import { Instance } from "@opencode-ai/core/instance" +import { EventV2 } from "@opencode-ai/core/event" +import { Location } from "@opencode-ai/core/location" import { ModelV2 } from "@opencode-ai/core/model" import { PluginV2 } from "@opencode-ai/core/plugin" import { ProviderV2 } from "@opencode-ai/core/provider" import { testEffect } from "./lib/effect" -const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" })) -const it = testEffect(Catalog.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer), Layer.provide(instanceLayer))) +const locationLayer = Layer.succeed(Location.Service, Location.Service.of({ directory: "test" })) +const it = testEffect( + Catalog.layer.pipe( + Layer.provideMerge(EventV2.defaultLayer), + Layer.provideMerge(PluginV2.defaultLayer), + Layer.provideMerge(locationLayer), + ), +) describe("CatalogV2", () => { it.effect("normalizes provider baseURL into endpoint url", () => @@ -69,6 +76,31 @@ describe("CatalogV2", () => { }), ) + it.effect("publishes model updated events", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const events = yield* EventV2.Service + const providerID = ProviderV2.ID.make("test") + const modelID = ModelV2.ID.make("model") + const fiber = yield* events + .subscribe(Catalog.Event.ModelUpdated) + .pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped) + + yield* Effect.yieldNow + yield* catalog.provider.update(providerID, () => {}) + yield* catalog.model.update(providerID, modelID, (model) => { + model.name = "Updated Model" + }) + const event = Array.from(yield* Fiber.join(fiber))[0] + + expect(event?.type).toBe("catalog.model.updated") + expect(event?.data.model.providerID).toBe(providerID) + expect(event?.data.model.id).toBe(modelID) + expect(event?.data.model.name).toBe("Updated Model") + expect(event?.location).toEqual({ directory: "test" }) + }), + ) + it.effect("resolves unknown model endpoint from provider endpoint", () => Effect.gen(function* () { const catalog = yield* Catalog.Service diff --git a/packages/core/test/event.test.ts b/packages/core/test/event.test.ts new file mode 100644 index 0000000000..b67b2897a1 --- /dev/null +++ b/packages/core/test/event.test.ts @@ -0,0 +1,132 @@ +import { describe, expect } from "bun:test" +import { Effect, Fiber, Layer, Schema, Stream } from "effect" +import { EventV2 } from "@opencode-ai/core/event" +import { Location } from "@opencode-ai/core/location" +import { testEffect } from "./lib/effect" + +const locationLayer = Layer.succeed( + Location.Service, + Location.Service.of({ directory: "project", workspaceID: "workspace" }), +) +const it = testEffect(EventV2.layer.pipe(Layer.provideMerge(locationLayer))) +const itWithoutLocation = testEffect(EventV2.layer) + +const Message = EventV2.define({ + type: "test.message", + schema: { + text: Schema.String, + }, +}) + +const GlobalMessage = EventV2.define({ + type: "test.global", + schema: { + text: Schema.String, + }, +}) + +const VersionedMessage = EventV2.define({ + type: "test.versioned", + version: 2, + schema: { + text: Schema.String, + }, +}) + +describe("EventV2", () => { + it.effect("publishes events with the current location", () => + Effect.gen(function* () { + const events = yield* EventV2.Service + const fiber = yield* events.subscribe(Message).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped) + yield* Effect.yieldNow + const event = yield* events.publish(Message, { text: "hello" }) + const received = Array.from(yield* Fiber.join(fiber)) + + expect(received).toEqual([event]) + expect(event.type).toBe("test.message") + expect(event).not.toHaveProperty("version") + expect(event.data).toEqual({ text: "hello" }) + expect(event.location).toEqual({ directory: "project", workspaceID: "workspace" }) + }), + ) + + itWithoutLocation.effect("omits location when no location is available", () => + Effect.gen(function* () { + const events = yield* EventV2.Service + const event = yield* events.publish(GlobalMessage, { text: "hello" }) + + expect(event).not.toHaveProperty("location") + expect(event.type).toBe("test.global") + }), + ) + + it.effect("publishes definition version", () => + Effect.gen(function* () { + const events = yield* EventV2.Service + const event = yield* events.publish(VersionedMessage, { text: "hello" }) + + expect(event.type).toBe("test.versioned") + expect(event.version).toBe(2) + }), + ) + + it.effect("stores definitions in the exported registry", () => + Effect.sync(() => { + expect(EventV2.registry.get(Message.type)).toBe(Message) + }), + ) + + it.effect("publishes to typed and wildcard subscriptions", () => + Effect.gen(function* () { + const events = yield* EventV2.Service + const typed = yield* events.subscribe(Message).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped) + const wildcard = yield* events.all().pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped) + yield* Effect.yieldNow + const event = yield* events.publish(Message, { text: "hello" }) + + expect(Array.from(yield* Fiber.join(typed))).toEqual([event]) + expect(Array.from(yield* Fiber.join(wildcard))).toEqual([event]) + }), + ) + + it.effect("runs sync handlers inline", () => + Effect.gen(function* () { + const events = yield* EventV2.Service + const received = new Array() + const unsubscribe = yield* events.sync((event) => + Effect.sync(() => { + received.push(event) + }), + ) + + const event = yield* events.publish(Message, { text: "hello" }) + yield* unsubscribe + yield* events.publish(Message, { text: "after unsubscribe" }) + + expect(received).toEqual([event]) + }), + ) + + it.effect("runs sync handlers before publishing to streams", () => + Effect.gen(function* () { + const events = yield* EventV2.Service + const received = new Array() + const fiber = yield* events.all().pipe( + Stream.take(1), + Stream.runForEach(() => Effect.sync(() => received.push("stream"))), + Effect.forkScoped, + ) + yield* events.sync((event) => + Effect.sync(() => { + received.push(event.type) + }), + ) + + yield* Effect.yieldNow + yield* events.publish(Message, { text: "hello" }) + yield* Fiber.join(fiber) + + expect(received).toEqual([Message.type, "stream"]) + }), + ) +}) diff --git a/packages/core/test/plugin/provider-opencode.test.ts b/packages/core/test/plugin/provider-opencode.test.ts index 8271a4a5c8..ed82686a21 100644 --- a/packages/core/test/plugin/provider-opencode.test.ts +++ b/packages/core/test/plugin/provider-opencode.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { DateTime, Effect, Layer, Option } from "effect" import { Catalog } from "@opencode-ai/core/catalog" -import { Instance } from "@opencode-ai/core/instance" +import { Location } from "@opencode-ai/core/location" import { ModelV2 } from "@opencode-ai/core/model" import { PluginV2 } from "@opencode-ai/core/plugin" import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode" @@ -9,7 +9,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider" import { it, model, provider, withEnv } from "./provider-helper" const cost = (input: number, output = 0) => [{ input, output, cache: { read: 0, write: 0 } }] -const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" })) +const locationLayer = Layer.succeed(Location.Service, Location.Service.of({ directory: "test" })) describe("OpencodePlugin", () => { it.effect("uses a public key and cancels paid models without credentials", () => @@ -192,6 +192,6 @@ describe("OpencodePlugin", () => { const selected = yield* catalog.model.small(providerID) expect(Option.getOrUndefined(selected)?.id).toBe(ModelV2.ID.make("gpt-5-nano")) - }).pipe(Effect.provide(Catalog.defaultLayer.pipe(Layer.provide(instanceLayer)))), + }).pipe(Effect.provide(Catalog.defaultLayer.pipe(Layer.provide(locationLayer)))), ) }) diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 41a7664d5b..f43d4ec9fa 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "7.3.45" +version = "7.2.49" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.45/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.49/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.45/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.49/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.45/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.49/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.45/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.49/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.45/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.49/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/kilo-docs/markdoc/partials/cli-commands-table.md b/packages/kilo-docs/markdoc/partials/cli-commands-table.md index 962447f0d1..877e4a1778 100644 --- a/packages/kilo-docs/markdoc/partials/cli-commands-table.md +++ b/packages/kilo-docs/markdoc/partials/cli-commands-table.md @@ -13,12 +13,14 @@ | `kilo upgrade [target]` | upgrade kilo to the latest or a specific version | | `kilo uninstall` | uninstall kilo and remove all related files | | `kilo serve` | starts a headless kilo server | +| `kilo web` | start kilo server and open web interface | | `kilo models [provider]` | list all available models | | `kilo roll-call ` | batch-test text models matching a filter for connectivity and latency | | `kilo profile` | show Kilo account profile | | `kilo stats` | show token usage and cost statistics | | `kilo export [sessionID]` | export session data as JSON | | `kilo import ` | import session data from JSON file or URL | +| `kilo github` | manage GitHub agent | | `kilo pr ` | fetch and checkout a GitHub PR branch, then run kilo | | `kilo session` | manage sessions | | `kilo remote` | enable remote connection for real-time session relay | diff --git a/packages/kilo-docs/public/img/npm-package-readme/kilo-cli.png b/packages/kilo-docs/public/img/npm-package-readme/kilo-cli.png new file mode 100644 index 0000000000..c045b7940b Binary files /dev/null and b/packages/kilo-docs/public/img/npm-package-readme/kilo-cli.png differ diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/diff-panel-with-diffs-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/diff-panel-with-diffs-chromium-linux.png new file mode 100644 index 0000000000..bae0b2e2b3 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/diff-panel-with-diffs-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:129ea4c06998d8418bdb8edc8e61e094849835edb3f40cf6e1ddc34568168e61 +size 52396 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-with-changes-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-with-changes-chromium-linux.png new file mode 100644 index 0000000000..37a0a3a3a8 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-with-changes-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bd31cd4e01c2e2a5ed579bce1b542368e7a11fc878d375a93fb238ba4f82469 +size 53288 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-with-collapsed-context-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-with-collapsed-context-chromium-linux.png new file mode 100644 index 0000000000..17e37e9de1 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-with-collapsed-context-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47b7b7bfb4a6b5631d8fdb808c1b3a9eb631b6f96911ddf15880f0c2fee4f7e0 +size 38558 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/chat-view-agent-manager-completed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/chat-view-agent-manager-completed-chromium-linux.png new file mode 100644 index 0000000000..0fd5ad4f06 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/chat-view-agent-manager-completed-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba55764c944231be2cd47c839d3a46c4bbdcb0a3060a1d8aa3ed7d391a34d722 +size 14749 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-subagent-to-queued-user-spacing-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-subagent-to-queued-user-spacing-chromium-linux.png new file mode 100644 index 0000000000..2aed188bd6 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-subagent-to-queued-user-spacing-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16c736bff5fba46d9e7d88e852ad70f96e37ad000280b03cb393da9c970e2ae9 +size 1415 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-tool-to-queued-user-spacing-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-tool-to-queued-user-spacing-chromium-linux.png new file mode 100644 index 0000000000..e4b892ac84 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-tool-to-queued-user-spacing-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:288e4d74c5e74773cea45e3b7cb428903ace337f9d27a3506c9ba193fe291b86 +size 3041 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/user-message-review-comments-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/user-message-review-comments-chromium-linux.png new file mode 100644 index 0000000000..606fab3eb7 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/user-message-review-comments-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ae8197b9d63cf977fa5112a1b02eb48bc22a1577b4dee15abd475805f0db5b0 +size 11769 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/bash-with-permission-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/bash-with-permission-chromium-linux.png new file mode 100644 index 0000000000..3a95e00a1e --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/bash-with-permission-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb1a94508ea38454e26d3fcc9449cf11ebecd1a5adcbda029ec3db4e45f2d06e +size 15121 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-bash-many-rules-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-bash-many-rules-chromium-linux.png new file mode 100644 index 0000000000..7524d83031 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-bash-many-rules-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebc03aa35f29a8f5ddfd841fc691b187c440348190e16d3be33de85b151ac193 +size 17886 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-heredoc-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-heredoc-chromium-linux.png new file mode 100644 index 0000000000..9f3cd34d95 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-heredoc-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bea86246a569ca4373ba9a17515a0366c0a78643aaed85bcb5cc663467335806 +size 22382 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-subagent-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-subagent-chromium-linux.png new file mode 100644 index 0000000000..c914f3c6f2 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-subagent-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d038d117b50c8e71fdddc507af5fa123d823d2b575565ef1aa68f7623c7d04c +size 12814 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/tool-errors-200-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/tool-errors-200-chromium-linux.png new file mode 100644 index 0000000000..34d6965b37 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/tool-errors-200-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3fb9398991f40c60fb16d73b05ba4dd85af3475928df65917fefb9af1c4aaf1 +size 7836 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/tool-errors-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/tool-errors-chromium-linux.png new file mode 100644 index 0000000000..9f0215286b --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/tool-errors-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8576fc52a9122900230f545f9b0886280251b3c636a7c2416432730c2844398 +size 8727 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/agent-behaviour-workflows-empty-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/agent-behaviour-workflows-empty-chromium-linux.png new file mode 100644 index 0000000000..e588cecbdd --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/agent-behaviour-workflows-empty-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe0ed476823eb03bc50fec4e9cdb656552aab3b07ee6ed3c30265ed41f0bfda0 +size 21344 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-kilo-catalog-loading-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-kilo-catalog-loading-chromium-linux.png new file mode 100644 index 0000000000..aac2e629fe --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-kilo-catalog-loading-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:add00c319d54f53c7c9859428b18182fa91da1b17e4c03b1ee8015ed08f73d19 +size 48653 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-kilo-model-preset-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-kilo-model-preset-chromium-linux.png new file mode 100644 index 0000000000..714c7562e7 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-kilo-model-preset-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9fbce155629b97a30ee9f95f226ef883b6febf8002a657516ff439e3fae0f6c +size 51829 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-provider-blur-race-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-provider-blur-race-chromium-linux.png new file mode 100644 index 0000000000..13e53ad7ee --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-provider-blur-race-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3330276aded477430e615ababb2caa7eeb2fa7f7406974b9b829e6092d5a619a +size 51792 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-scope-switch-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-scope-switch-chromium-linux.png new file mode 100644 index 0000000000..49e6bf669a --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-scope-switch-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb78008c0fcbc47cb4b73fcea0739ce975c64b31d58852d74fd31078f3774694 +size 54958 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-200-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-200-chromium-linux.png new file mode 100644 index 0000000000..ade4c0398d --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-200-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:001db2cec148c97a321076e45ec4478beced1bd18f0f4347927419d7194d6a43 +size 31986 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-default-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-default-chromium-linux.png new file mode 100644 index 0000000000..2597a60c84 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-default-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3f86dabc39700b3a86508c1e6499a546eeabf720832b62c9ce12f2d97378ebd +size 34649 diff --git a/packages/kilo-docs/source-links.md b/packages/kilo-docs/source-links.md index 21afcdcab9..5ae4764c68 100644 --- a/packages/kilo-docs/source-links.md +++ b/packages/kilo-docs/source-links.md @@ -61,6 +61,8 @@ - +- + - - diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 129653561b..78e49de211 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -2852,8 +2852,15 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper currentSessionId: this.currentSession?.id, trackedSessionIds: this.trackedSessionIds, sessionDirectories: this.sessionDirectories, + extraDirectories: this.opts.worktreeDirectories, postMessage: (msg: unknown) => this.postMessage(msg), getWorkspaceDirectory: (sid?: string) => this.getWorkspaceDirectory(sid), + recordQuestionDirectory: (id: string, dir: string) => this.connectionService.recordQuestionDirectory(id, dir), + getQuestionDirectory: (id: string) => this.connectionService.getQuestionDirectory(id), + clearQuestionDirectory: (id: string) => this.connectionService.clearQuestionDirectory(id), + getQuestionRevision: () => this.connectionService.getQuestionRevision(), + pruneQuestionDirectories: (active: Set, dirs: Set) => + this.connectionService.pruneQuestionDirectories(active, dirs), } } diff --git a/packages/kilo-vscode/src/kilo-provider/handlers/question.ts b/packages/kilo-vscode/src/kilo-provider/handlers/question.ts index 69d3796000..477db76803 100644 --- a/packages/kilo-vscode/src/kilo-provider/handlers/question.ts +++ b/packages/kilo-vscode/src/kilo-provider/handlers/question.ts @@ -6,15 +6,53 @@ * No vscode dependency. */ -import type { KiloClient } from "@kilocode/sdk/v2/client" +import type { KiloClient, QuestionRequest } from "@kilocode/sdk/v2/client" -interface QuestionContext { +export interface QuestionContext { readonly client: KiloClient | null readonly currentSessionId: string | undefined readonly trackedSessionIds: Set readonly sessionDirectories: ReadonlyMap + readonly extraDirectories?: () => string[] postMessage(msg: unknown): void getWorkspaceDirectory(sessionId?: string): string + recordQuestionDirectory(requestID: string, directory: string): void + getQuestionDirectory(requestID: string): string | undefined + clearQuestionDirectory(requestID: string): void + getQuestionRevision(): number + pruneQuestionDirectories(active: Set, dirs: Set): void +} + +interface QuestionRecovery { + readonly seen: Set + readonly complete: boolean +} + +function isNotFoundError(error: unknown): boolean { + const record = (value: unknown) => + value && typeof value === "object" ? (value as Record) : undefined + const obj = record(error) + if (!obj) return false + + const cause = record(obj.cause) + const body = record(cause?.body) + return [obj, record(obj.data), cause, body, record(body?.data)].some( + (value) => value?.name === "NotFoundError" || value?._tag === "NotFound" || value?.status === 404, + ) +} + +function stale(ctx: QuestionContext, requestID: string): void { + ctx.clearQuestionDirectory(requestID) + ctx.postMessage({ type: "questionResolved", requestID }) + void fetchAndSendPendingQuestions(ctx) +} + +async function recover(ctx: QuestionContext, requestID: string): Promise { + const result = await fetchAndSendPendingQuestions(ctx) + if (!result?.complete || result.seen.has(requestID)) return false + ctx.clearQuestionDirectory(requestID) + ctx.postMessage({ type: "questionResolved", requestID }) + return true } /** @@ -23,29 +61,52 @@ interface QuestionContext { * called after child-session sync and after SSE reconnects so missed * question.asked events don't leave the server blocked indefinitely. */ -export async function fetchAndSendPendingQuestions(ctx: QuestionContext): Promise { +export async function fetchAndSendPendingQuestions(ctx: QuestionContext): Promise { if (!ctx.client) return try { - const dirs = new Set([ctx.getWorkspaceDirectory(), ...ctx.sessionDirectories.values()]) - const seen = new Set() - for (const dir of dirs) { - const { data } = await ctx.client.question.list({ directory: dir }) - if (!data) continue - for (const q of data) { - if (seen.has(q.id)) continue - seen.add(q.id) - if (!ctx.trackedSessionIds.has(q.sessionID)) continue + for (;;) { + const dirs = new Set([ + ctx.getWorkspaceDirectory(), + ...ctx.sessionDirectories.values(), + ...(ctx.extraDirectories?.() ?? []), + ]) + const revision = ctx.getQuestionRevision() + const seen = new Set() + const scanned = new Set() + const failed = new Set() + const pending: Array<{ question: QuestionRequest; dir: string }> = [] + for (const dir of dirs) { + const { data, error } = await ctx.client.question.list({ directory: dir }) + if (error) { + failed.add(dir) + console.error(`[Kilo New] KiloProvider: Failed to fetch pending questions for ${dir}:`, error) + continue + } + scanned.add(dir) + if (!data) continue + for (const q of data) { + if (seen.has(q.id)) continue + seen.add(q.id) + if (!ctx.trackedSessionIds.has(q.sessionID)) continue + pending.push({ question: q, dir }) + } + } + if (ctx.getQuestionRevision() !== revision) continue + for (const item of pending) { + ctx.recordQuestionDirectory(item.question.id, item.dir) ctx.postMessage({ type: "questionRequest", question: { - id: q.id, - sessionID: q.sessionID, - questions: q.questions, - blocking: q.blocking, - tool: q.tool, + id: item.question.id, + sessionID: item.question.sessionID, + questions: item.question.questions, + blocking: item.question.blocking, + tool: item.question.tool, }, }) } + ctx.pruneQuestionDirectories(seen, scanned) + return { seen, complete: failed.size === 0 } } } catch (error) { console.error("[Kilo New] KiloProvider: Failed to fetch pending questions:", error) @@ -65,14 +126,19 @@ export async function handleQuestionReply( } const sid = sessionID ?? ctx.currentSessionId + const origin = ctx.getQuestionDirectory(requestID) + const dir = origin ?? ctx.getWorkspaceDirectory(sid) try { - await ctx.client.question.reply( - { requestID, answers, directory: ctx.getWorkspaceDirectory(sid) }, - { throwOnError: true }, - ) + await ctx.client.question.reply({ requestID, answers, directory: dir }, { throwOnError: true }) + ctx.clearQuestionDirectory(requestID) return true } catch (error) { + if (isNotFoundError(error) && origin) { + stale(ctx, requestID) + return false + } + if (isNotFoundError(error) && (await recover(ctx, requestID))) return false console.error("[Kilo New] KiloProvider: Failed to reply to question:", error) ctx.postMessage({ type: "questionError", requestID }) return false @@ -91,11 +157,19 @@ export async function handleQuestionReject( } const sid = sessionID ?? ctx.currentSessionId + const origin = ctx.getQuestionDirectory(requestID) + const dir = origin ?? ctx.getWorkspaceDirectory(sid) try { - await ctx.client.question.reject({ requestID, directory: ctx.getWorkspaceDirectory(sid) }, { throwOnError: true }) + await ctx.client.question.reject({ requestID, directory: dir }, { throwOnError: true }) + ctx.clearQuestionDirectory(requestID) return true } catch (error) { + if (isNotFoundError(error) && origin) { + stale(ctx, requestID) + return false + } + if (isNotFoundError(error) && (await recover(ctx, requestID))) return false console.error("[Kilo New] KiloProvider: Failed to reject question:", error) ctx.postMessage({ type: "questionError", requestID }) return false diff --git a/packages/kilo-vscode/src/kiloclaw/KiloClawProvider.ts b/packages/kilo-vscode/src/kiloclaw/KiloClawProvider.ts index b6530ccc70..5d00764d52 100644 --- a/packages/kilo-vscode/src/kiloclaw/KiloClawProvider.ts +++ b/packages/kilo-vscode/src/kiloclaw/KiloClawProvider.ts @@ -399,7 +399,7 @@ export class KiloClawProvider implements vscode.Disposable { return false } - this.attachEventHandlers(events, chat) + this.attachEventHandlers(events) this.subscribeSandboxContext() return true } @@ -511,7 +511,7 @@ export class KiloClawProvider implements vscode.Disposable { this.subscribedConversationContext = null } - private attachEventHandlers(events: EventServiceClient, _chat: KiloChatClient): void { + private attachEventHandlers(events: EventServiceClient): void { // Reset on reconnect — the event stream may have missed events while // disconnected, so refetch authoritative state. const offReconnect = events.onReconnect(() => { diff --git a/packages/kilo-vscode/src/kiloclaw/kilo-chat-client.ts b/packages/kilo-vscode/src/kiloclaw/kilo-chat-client.ts index 5583b32af5..8d402dafe8 100644 --- a/packages/kilo-vscode/src/kiloclaw/kilo-chat-client.ts +++ b/packages/kilo-vscode/src/kiloclaw/kilo-chat-client.ts @@ -89,10 +89,6 @@ export class KiloChatClient { }) } - getConversation(conversationId: string): Promise { - return this.request(`/v1/conversations/${conversationId}`) - } - createConversation(req: { sandboxId: string title?: string diff --git a/packages/kilo-vscode/src/kiloclaw/token-manager.ts b/packages/kilo-vscode/src/kiloclaw/token-manager.ts index 745b9c7c3f..93020e67d6 100644 --- a/packages/kilo-vscode/src/kiloclaw/token-manager.ts +++ b/packages/kilo-vscode/src/kiloclaw/token-manager.ts @@ -26,11 +26,6 @@ export class TokenManager { constructor(private readonly getClient: () => KiloClient | null) {} - /** Latest resolved token info (may be stale). Used for URL extraction. */ - peek(): ChatToken | null { - return this.cached - } - /** Drop the cached token; next `get` will refetch. */ clear(): void { this.cached = null diff --git a/packages/kilo-vscode/src/provider-actions.ts b/packages/kilo-vscode/src/provider-actions.ts index cae5ba1e0c..32623f5a64 100644 --- a/packages/kilo-vscode/src/provider-actions.ts +++ b/packages/kilo-vscode/src/provider-actions.ts @@ -18,6 +18,12 @@ import { configFeatures } from "./features" */ type AuthState = "api" | "oauth" | "wellknown" +/** API key retained extension-side for authenticated model fetches (#10139). */ +export interface StoredProviderKey { + key: string + baseURL: string +} + function disabledWithout(list: string[] | undefined, id: string) { return (list ?? []).filter((item) => item !== id) } @@ -59,17 +65,43 @@ export async function fetchProviderData(client: KiloClient, dir: string) { authRequest, ]) const authStates: Record = {} + const storedKeys: Record = {} const all = response.all.map((item) => { const raw = item as Record if (typeof raw.id === "string" && typeof raw.key === "string" && raw.key) { authStates[raw.id] = "api" + // Retain the key on the extension side so model fetches for an existing + // provider can authenticate without the webview ever seeing the secret + // (#10139). Only providers with a configured baseURL are retained — the + // fetch handler requires a URL match before applying a stored key. + const options = record(raw.options) ? raw.options : undefined + const baseURL = options && typeof options.baseURL === "string" ? options.baseURL : undefined + if (baseURL) storedKeys[raw.id] = { key: raw.key, baseURL } } if (!("key" in raw)) return item const next = { ...raw } delete next.key return next as (typeof response.all)[number] }) - return { response: { ...response, all }, authMethods, authStates } + return { response: { ...response, all }, authMethods, authStates, storedKeys } +} + +/** + * Resolve the stored API key for a model fetch on an existing provider. + * The key is only applied when the requested URL matches the provider's + * configured baseURL, so a stored secret can never be redirected to a + * different host (e.g. after the user edits the URL field). + */ +export function resolveStoredKey( + storedKeys: Record, + providerID: unknown, + url: string, +): string | undefined { + if (typeof providerID !== "string" || !providerID) return undefined + const stored = storedKeys[providerID] + if (!stored) return undefined + const normalize = (value: string) => value.trim().replace(/\/+$/, "") + return normalize(stored.baseURL) === normalize(url) ? stored.key : undefined } export function buildActionContext( diff --git a/packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts b/packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts index 5c24286e78..7a2eeaebba 100644 --- a/packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts +++ b/packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts @@ -257,37 +257,6 @@ export class AutocompleteServiceManager { return Date.now() < snoozeUntil } - /** - * Get remaining snooze time in seconds - */ - public getSnoozeRemainingSeconds(): number { - const snoozeUntil = this.settings?.snoozeUntil - if (!snoozeUntil) { - return 0 - } - const remaining = Math.max(0, Math.ceil((snoozeUntil - Date.now()) / 1000)) - return remaining - } - - /** - * Snooze autocomplete for a specified number of seconds - */ - public async snooze(seconds: number): Promise { - if (this.snoozeTimer) { - clearTimeout(this.snoozeTimer) - this.snoozeTimer = null - } - - const snoozeUntil = Date.now() + seconds * 1000 - await writeSettings({ snoozeUntil }) - - this.snoozeTimer = setTimeout(() => { - void this.unsnooze() - }, seconds * 1000) - - await this.load() - } - /** * Cancel snooze and re-enable autocomplete */ diff --git a/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts b/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts index 1244b25b9e..5fc37a76bf 100644 --- a/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts +++ b/packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts @@ -84,10 +84,6 @@ vi.mock("../classic-auto-complete/AutocompleteInlineCompletionProvider", () => { public setModel(id: string) { this.modelId = id } - public getModelId(): string { - return this.modelId - } - constructor(..._args: any[]) {} } return { AutocompleteInlineCompletionProvider } @@ -317,21 +313,5 @@ describe("AutocompleteServiceManager (less mocked logic)", () => { expect(manager.isSnoozed()).toBe(true) }) - - it("getSnoozeRemainingSeconds() returns 0 when not snoozed", async () => { - const manager = await createManager() - ;(manager as any).settings = {} - - expect(manager.getSnoozeRemainingSeconds()).toBe(0) - }) - - it("getSnoozeRemainingSeconds() returns a positive number when snoozed", async () => { - const manager = await createManager() - ;(manager as any).settings = { snoozeUntil: Date.now() + 30_000 } - - const remaining = manager.getSnoozeRemainingSeconds() - expect(remaining).toBeGreaterThan(0) - expect(remaining).toBeLessThanOrEqual(30) - }) }) }) diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts index 6e6eef24c7..cc38a899e4 100644 --- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts +++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts @@ -233,10 +233,6 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple this.contextProvider.modelId = modelId } - public getModelId(): string { - return this.contextProvider.modelId - } - private processSuggestion( suggestionText: string, prefix: string, diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/ErrorBackoff.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/ErrorBackoff.ts index 0d211aea23..622398b313 100644 --- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/ErrorBackoff.ts +++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/ErrorBackoff.ts @@ -133,13 +133,6 @@ export class ErrorBackoff { return false } - /** - * Whether a fatal (non-retriable) error is active — credits depleted, auth invalid, etc. - */ - isFatal(): boolean { - return this.fatal !== null - } - /** * The HTTP status code of the fatal error, or null. */ diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteContextProvider.test.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteContextProvider.test.ts index fc75473d7d..d91c07fc41 100644 --- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteContextProvider.test.ts +++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteContextProvider.test.ts @@ -55,9 +55,7 @@ vi.mock("../../continuedev/core/autocomplete/snippets/getAllSnippets", () => ({ rootPathSnippets: [], recentlyEditedRangeSnippets: [], recentlyVisitedRangesSnippets: [], - diffSnippets: [], clipboardSnippets: [], - ideSnippets: [], staticSnippet: [], }), })) @@ -133,9 +131,7 @@ describe("AutocompleteContextProvider", () => { rootPathSnippets: [], recentlyEditedRangeSnippets: [], recentlyVisitedRangesSnippets: [], - diffSnippets: [], clipboardSnippets: [], - ideSnippets: [], staticSnippet: [], }) @@ -178,9 +174,7 @@ describe("AutocompleteContextProvider", () => { rootPathSnippets: [], recentlyEditedRangeSnippets: [], recentlyVisitedRangesSnippets: [], - diffSnippets: [], clipboardSnippets: [], - ideSnippets: [], staticSnippet: [], }) @@ -275,9 +269,7 @@ describe("AutocompleteContextProvider", () => { rootPathSnippets: [], recentlyEditedRangeSnippets: [], recentlyVisitedRangesSnippets: [], - diffSnippets: [], clipboardSnippets: [], - ideSnippets: [], staticSnippet: [], }) @@ -318,12 +310,6 @@ describe("AutocompleteContextProvider", () => { rootPathSnippets: [], recentlyEditedRangeSnippets: [], recentlyVisitedRangesSnippets: [], - diffSnippets: [ - { - content: "diff content", - type: AutocompleteSnippetType.Diff, - }, - ], clipboardSnippets: [ { content: "clipboard content", @@ -331,14 +317,12 @@ describe("AutocompleteContextProvider", () => { copiedAt: "2024-01-01", }, ], - ideSnippets: [], staticSnippet: [], }) const { getSnippets } = await import("../../continuedev/core/autocomplete/templating/filtering") ;(getSnippets as any).mockImplementation((_helper: any, payload: any) => [ ...payload.recentlyOpenedFileSnippets, - ...payload.diffSnippets, ...payload.clipboardSnippets, ]) @@ -357,11 +341,9 @@ describe("AutocompleteContextProvider", () => { result.snippetsWithUris.some((s) => "filepath" in s && s.filepath && s.filepath.includes("blocked.ts")), ).toBe(false) // But should contain snippets without file paths - expect(result.snippetsWithUris).toHaveLength(2) - expect(result.snippetsWithUris[0].content).toBe("diff content") - expect(result.snippetsWithUris[0].type).toBe(AutocompleteSnippetType.Diff) - expect(result.snippetsWithUris[1].content).toBe("clipboard content") - expect(result.snippetsWithUris[1].type).toBe(AutocompleteSnippetType.Clipboard) + expect(result.snippetsWithUris).toHaveLength(1) + expect(result.snippetsWithUris[0].content).toBe("clipboard content") + expect(result.snippetsWithUris[0].type).toBe(AutocompleteSnippetType.Clipboard) }) it("should allow all files when no ignore controller is provided", async () => { @@ -389,9 +371,7 @@ describe("AutocompleteContextProvider", () => { rootPathSnippets: [], recentlyEditedRangeSnippets: [], recentlyVisitedRangesSnippets: [], - diffSnippets: [], clipboardSnippets: [], - ideSnippets: [], staticSnippet: [], }) diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/getProcessedSnippets.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/getProcessedSnippets.ts index 68b874a0b1..0e3900983e 100644 --- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/getProcessedSnippets.ts +++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/getProcessedSnippets.ts @@ -4,7 +4,6 @@ import { VsCodeIde } from "../continuedev/core/vscode-test-harness/src/VSCodeIde import { AutocompleteInput } from "../types" import { HelperVars } from "../continuedev/core/autocomplete/util/HelperVars" import { getAllSnippetsWithoutRace } from "../continuedev/core/autocomplete/snippets/getAllSnippets" -import { getDefinitionsFromLsp } from "../continuedev/core/vscode-test-harness/src/autocomplete/lsp" import { DEFAULT_AUTOCOMPLETE_OPTS } from "../continuedev/core/util/parameters" import { getSnippets } from "../continuedev/core/autocomplete/templating/filtering" import { FileIgnoreController } from "../shims/FileIgnoreController" @@ -97,7 +96,6 @@ export async function getProcessedSnippets( const snippetPayload = await getAllSnippetsWithoutRace({ helper, ide, - getDefinitionsFromLsp, contextRetrievalService: contextService, }) diff --git a/packages/kilo-vscode/src/services/autocomplete/context/VisibleCodeTracker.ts b/packages/kilo-vscode/src/services/autocomplete/context/VisibleCodeTracker.ts index dfe2b6a8f2..f1be897233 100644 --- a/packages/kilo-vscode/src/services/autocomplete/context/VisibleCodeTracker.ts +++ b/packages/kilo-vscode/src/services/autocomplete/context/VisibleCodeTracker.ts @@ -24,8 +24,6 @@ import { extractDiffInfo as _extractDiffInfo } from "./visible-code-utils" const GIT_SCHEMES = ["git", "gitfs", "file", "vscode-remote"] export class VisibleCodeTracker { - private lastContext: VisibleCodeContext | null = null - constructor( private workspacePath: string, private ignoreController: FileIgnoreController | null = null, @@ -101,19 +99,10 @@ export class VisibleCodeTracker { }) } - this.lastContext = { + return { timestamp: Date.now(), editors: editorInfos, } - - return this.lastContext - } - - /** - * Returns the last captured context, or null if never captured. - */ - public getLastContext(): VisibleCodeContext | null { - return this.lastContext } /** diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/constants/AutocompleteLanguageInfo.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/constants/AutocompleteLanguageInfo.ts index 02f14eb85a..aee32df923 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/constants/AutocompleteLanguageInfo.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/constants/AutocompleteLanguageInfo.ts @@ -1,6 +1,15 @@ import { getUriFileExtension } from "../../util/uri" import { BracketMatchingService } from "../filtering/BracketMatchingService" -import { CharacterFilter, LineFilter } from "../filtering/streamTransforms/lineStream" + +type LineStream = AsyncGenerator +type LineFilter = (args: { lines: LineStream; fullStop: () => void }) => LineStream +type CharacterFilter = (args: { + chars: AsyncGenerator + prefix: string + suffix: string + filepath: string + multiline: boolean +}) => AsyncGenerator export interface AutocompleteLanguageInfo { /** diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.test.ts deleted file mode 100644 index a092a205af..0000000000 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { describe, expect, it } from "vitest" -import { rankAndOrderSnippets, fillPromptWithSnippets } from "./index" -import { RankedSnippet } from "../../types" - -// vibecoded -describe("rankAndOrderSnippets", () => { - it("should rank and order snippets by similarity to cursor context", () => { - // Create a simple mock HelperVars with only the required properties - const mockHelper = { - fullPrefix: "function calculateTotal(items) {\n const total = items.reduce(", - fullSuffix: ", 0);\n return total;\n}", - options: { - slidingWindowSize: 50, - slidingWindowPrefixPercentage: 0.5, - }, - } as any - - // Create test snippets with different levels of similarity to the cursor context - const snippets: RankedSnippet[] = [ - { - filepath: "utils.ts", - range: { - start: { line: 10, character: 0 }, - end: { line: 12, character: 0 }, - }, - contents: "// Helper function for database queries\nfunction queryDb() {}", - }, - { - filepath: "math.ts", - range: { - start: { line: 5, character: 0 }, - end: { line: 7, character: 0 }, - }, - contents: "// Array reduce function\nconst sum = items.reduce((acc, item) => acc + item, 0);", - }, - { - filepath: "helpers.ts", - range: { - start: { line: 20, character: 0 }, - end: { line: 22, character: 0 }, - }, - contents: - "// Calculate total with reduce\nfunction calculateSum(items) {\n return items.reduce((a, b) => a + b);\n}", - }, - ] - - const result = rankAndOrderSnippets(snippets, mockHelper) - - // Verify the result has the expected structure - expect(result).toHaveLength(3) - - // All snippets should have scores assigned - expect(result.every((s) => typeof s.score === "number")).toBe(true) - - // All snippets should have required properties - result.forEach((snippet) => { - expect(snippet.filepath).toBeDefined() - expect(snippet.range).toBeDefined() - expect(snippet.contents).toBeDefined() - expect(snippet.score).toBeGreaterThanOrEqual(0) - }) - - // Scores should be in ascending order (lower scores = better matches) - for (let i = 0; i < result.length - 1; i++) { - expect(result[i].score).toBeLessThanOrEqual(result[i + 1].score) - } - }) -}) -// vibecoded - -describe("fillPromptWithSnippets", () => { - it("should fill token budget with snippets until limit is reached", () => { - // Create snippets with required properties (including score) - const snippets: Required[] = [ - { - filepath: "math.ts", - range: { - start: { line: 1, character: 0 }, - end: { line: 3, character: 0 }, - }, - contents: "function add(a, b) { return a + b; }", - score: 0.1, - }, - { - filepath: "utils.ts", - range: { - start: { line: 5, character: 0 }, - end: { line: 7, character: 0 }, - }, - contents: "function multiply(x, y) { return x * y; }", - score: 0.2, - }, - { - filepath: "helpers.ts", - range: { - start: { line: 10, character: 0 }, - end: { line: 15, character: 0 }, - }, - contents: "function calculateSum(items) {\n return items.reduce((acc, item) => acc + item, 0);\n}", - score: 0.3, - }, - ] - - // Set token limit to include first 2 snippets but not the third - // Using a model name and a reasonable token limit - const maxSnippetTokens = 25 - const modelName = "gpt-3.5-turbo" - - const result = fillPromptWithSnippets(snippets, maxSnippetTokens, modelName) - - // Verify that we got fewer snippets than we started with - expect(result.length).toBeLessThan(snippets.length) - expect(result.length).toBeGreaterThan(0) - - // Verify all returned snippets are from the original list - result.forEach((snippet) => { - expect(snippets).toContainEqual(snippet) - }) - - // Verify snippets maintain their order (first snippets are kept) - for (let i = 0; i < result.length; i++) { - expect(result[i]).toBe(snippets[i]) - } - }) -}) diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.ts index d0115a12f2..79095acb40 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.ts @@ -1,133 +1,9 @@ -import { RangeInFileWithContents } from "../../../" -import { countTokens } from "../../../llm/countTokens" -import { RankedSnippet } from "../../types" -import { HelperVars } from "../../util/HelperVars" - const rx = /[\s.,/#!$%^&*;:{}=\-_`~()[\]]/g + export function getSymbolsForSnippet(snippet: string): Set { const symbols = snippet .split(rx) - .map((s) => s.trim()) - .filter((s) => s !== "") + .map((symbol) => symbol.trim()) + .filter((symbol) => symbol !== "") return new Set(symbols) } - -/** - * Calculate similarity as number of shared symbols divided by total number of unique symbols between both. - */ -function jaccardSimilarity(a: string, b: string): number { - const aSet = getSymbolsForSnippet(a) - const bSet = getSymbolsForSnippet(b) - const union = new Set([...aSet, ...bSet]).size - - // Avoid division by zero - if (union === 0) { - return 0 - } - - let intersection = 0 - for (const symbol of aSet) { - if (bSet.has(symbol)) { - intersection++ - } - } - - return intersection / union -} - -/** - * Rank code snippets to be used in tab-autocomplete prompt. Returns a sorted version of the snippet array. - */ -export function rankAndOrderSnippets(ranges: RankedSnippet[], helper: HelperVars): Required[] { - //MINIMAL_REPO - this isn't actually used in continue - const windowAroundCursor = - helper.fullPrefix.slice(-helper.options.slidingWindowSize * helper.options.slidingWindowPrefixPercentage) + - helper.fullSuffix.slice(helper.options.slidingWindowSize * (1 - helper.options.slidingWindowPrefixPercentage)) - - const snippets: Required[] = ranges.map((snippet) => ({ - score: snippet.score ?? jaccardSimilarity(snippet.contents, windowAroundCursor), - ...snippet, - })) - const uniqueSnippets = deduplicateSnippets(snippets) - return uniqueSnippets.sort((a, b) => a.score - b.score) -} - -/** - * Deduplicate code snippets by merging overlapping ranges into a single range. - */ -function deduplicateSnippets(snippets: Required[]): Required[] { - // Group by file - const fileGroups: { - [key: string]: Required[] - } = {} - for (const snippet of snippets) { - if (!fileGroups[snippet.filepath]) { - fileGroups[snippet.filepath] = [] - } - fileGroups[snippet.filepath].push(snippet) - } - - // Merge overlapping ranges - const allRanges = [] - for (const file of Object.keys(fileGroups)) { - allRanges.push(...mergeSnippetsByRange(fileGroups[file])) - } - return allRanges -} - -function mergeSnippetsByRange(snippets: Required[]): Required[] { - if (snippets.length <= 1) { - return snippets - } - - const sorted = snippets.sort((a, b) => a.range.start.line - b.range.start.line) - const merged: Required[] = [] - - while (sorted.length > 0) { - const next = sorted.shift()! - const last = merged[merged.length - 1] - if (merged.length > 0 && last.range.end.line >= next.range.start.line) { - // Merge with previous snippet - last.score = Math.max(last.score, next.score) - try { - last.range.end = next.range.end - } catch (e) { - console.log("Error merging ranges", e) - } - last.contents = mergeOverlappingRangeContents(last, next) - } else { - merged.push(next) - } - } - - return merged -} - -function mergeOverlappingRangeContents(first: RangeInFileWithContents, second: RangeInFileWithContents): string { - const firstLines = first.contents.split("\n") - const numOverlapping = first.range.end.line - second.range.start.line - return `${firstLines.slice(-numOverlapping).join("\n")}\n${second.contents}` -} - -/** - * Fill the allowed space with snippets. - * It is assumed that the snippets are sorted by score. - */ -export function fillPromptWithSnippets( //MINIMAL_REPO - this isn't actually used in continue - snippets: Required[], - maxSnippetTokens: number, - modelName: string, -): Required[] { - let tokensRemaining = maxSnippetTokens - const keptSnippets: Required[] = [] - for (let i = 0; i < snippets.length; i++) { - const snippet = snippets[i] - const tokenCount = countTokens(snippet.contents, modelName) - if (tokensRemaining - tokenCount >= 0) { - tokensRemaining -= tokenCount - keptSnippets.push(snippet) - } - } - - return keptSnippets -} diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/static-context/StaticContextService.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/static-context/StaticContextService.ts index 3a3609ccaf..894205a7d5 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/static-context/StaticContextService.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/static-context/StaticContextService.ts @@ -48,31 +48,6 @@ export class StaticContextService { }) } - public static formatAutocompleteStaticSnippet(ctx: StaticContext): string { - let output = `AutocompleteStaticSnippet:\n` - output += ` holeType: ${ctx.holeType}\n` - - output += ` relevantTypes:\n` - if (ctx.relevantTypes.size === 0) { - output += ` (none)\n` - } else { - ctx.relevantTypes.forEach((types, filepath) => { - output += ` ${filepath}: [${types.join(", ")}]\n` - }) - } - - output += ` relevantHeaders:\n` - if (ctx.relevantHeaders.size === 0) { - output += ` (none)\n` - } else { - ctx.relevantHeaders.forEach((headers, filepath) => { - output += ` ${filepath}: [${headers.join(", ")}]\n` - }) - } - - return output - } - public async getContext(helper: HelperVars): Promise { const tsFiles = await this.getTypeScriptFilesFromWorkspaces(helper.workspaceUris) // Get the three contexts holeContext, relevantTypes, relevantHeaders. diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.test.ts index 6a205b77e5..bce4ab033c 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.test.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect, beforeEach } from "vitest" -import { BracketMatchingService, BRACKETS, BRACKETS_REVERSE } from "./BracketMatchingService" +import { beforeEach, describe, expect, it } from "vitest" +import { BRACKETS, BRACKETS_REVERSE, BracketMatchingService } from "./BracketMatchingService" describe("BracketMatchingService", () => { let service: BracketMatchingService @@ -8,300 +8,93 @@ describe("BracketMatchingService", () => { service = new BracketMatchingService() }) - describe("BRACKETS constants", () => { - it("should have correct opening-to-closing bracket mappings", () => { - expect(BRACKETS["("]).toBe(")") - expect(BRACKETS["{"]).toBe("}") - expect(BRACKETS["["]).toBe("]") - }) - - it("should have correct closing-to-opening bracket mappings", () => { - expect(BRACKETS_REVERSE[")"]).toBe("(") - expect(BRACKETS_REVERSE["}"]).toBe("{") - expect(BRACKETS_REVERSE["]"]).toBe("[") - }) + it("defines matching bracket pairs", () => { + expect(BRACKETS).toEqual({ "(": ")", "{": "}", "[": "]" }) + expect(BRACKETS_REVERSE).toEqual({ ")": "(", "}": "{", "]": "[" }) }) - describe("handleAcceptedCompletion", () => { - it("should track unmatched opening brackets from completion", () => { - service.handleAcceptedCompletion("function test() {", "test.ts") - // Internal state should track the unmatched opening brackets - // We can verify this by checking behavior in subsequent calls - }) - - it("should handle matched bracket pairs correctly", () => { - service.handleAcceptedCompletion("function test() { return 1; }", "test.ts") - // All brackets are matched, so stack should be empty - }) - - it("should handle multiple unmatched opening brackets", () => { - service.handleAcceptedCompletion("if (condition) { while (true) {", "test.ts") - // Should track both unmatched { brackets - }) - - it("should handle nested bracket structures", () => { - service.handleAcceptedCompletion("arr[0] = { key: [1, 2]", "test.ts") - // Should track unmatched { and [ - }) - - it("should stop tracking when encountering unmatched closing bracket", () => { - service.handleAcceptedCompletion("function test() { } }", "test.ts") - // Should stop when encountering the extra closing brace - }) - - it("should handle different bracket types", () => { - service.handleAcceptedCompletion("const obj = { arr: [1, (2", "test.ts") - // Should track {, [, and ( - }) - - it("should reset state for each new completion", () => { - service.handleAcceptedCompletion("function test() {", "test.ts") - service.handleAcceptedCompletion("class MyClass {", "test.ts") - // Second call should reset state from first call - }) - - it("should update filepath tracking", () => { - service.handleAcceptedCompletion("function a() {", "file1.ts") - service.handleAcceptedCompletion("function b() {", "file2.ts") - // Should track that we're now in file2.ts - }) - - it("should handle empty completion string", () => { - service.handleAcceptedCompletion("", "test.ts") - // Should not throw and should have empty stack - }) - - it("should handle completion with only text and no brackets", () => { - service.handleAcceptedCompletion("const x = 5;", "test.ts") - // Should complete successfully with empty bracket stack - }) - - it("should handle complex nested structure", () => { - service.handleAcceptedCompletion("obj = { a: [1, { b: (x", "test.ts") - // Should track {, [, {, ( - }) + async function* stream(chunks: string[]): AsyncGenerator { + for (const chunk of chunks) yield chunk + } + + async function collect(gen: AsyncGenerator): Promise { + const chunks: string[] = [] + for await (const chunk of gen) chunks.push(chunk) + return chunks.join("") + } + + it("allows a matching single-line closing bracket", async () => { + const result = service.stopOnUnmatchedClosingBracket( + stream(["x + 1)"]), + "const result = calculate(", + ");", + "test.ts", + false, + ) + expect(await collect(result)).toBe("x + 1)") }) - describe("stopOnUnmatchedClosingBracket", () => { - // Helper function to create async generator from array - async function* arrayToAsyncGen(arr: string[]): AsyncGenerator { - for (const item of arr) { - yield item - } - } - - // Helper to collect all values from async generator - async function collectAll(gen: AsyncGenerator): Promise { - const results: string[] = [] - for await (const item of gen) { - results.push(item) - } - return results - } - - describe("multiline mode", () => { - it("should allow closing brackets that match previous completion", async () => { - service.handleAcceptedCompletion("function test() {", "test.ts") - const stream = arrayToAsyncGen(["\n return 1;\n}"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "function test() ", "", "test.ts", true) - const result = await collectAll(filtered) - expect(result.join("")).toBe("\n return 1;\n}") - }) - - it("should not use previous completion state from different file", async () => { - service.handleAcceptedCompletion("function test() {", "file1.ts") - const stream = arrayToAsyncGen(["}"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "", "", "file2.ts", true) - const result = await collectAll(filtered) - // Different file so stack is empty, but '}' is in whitespace section - // Whitespace section (closing brackets before non-whitespace) yields without checking - // Since '}' doesn't match /[^\s\)\}\]]/, it's all whitespace/closing brackets - // So entire chunk is yielded and loop continues to end - expect(result.join("")).toBe("}") - }) - - it("should stop on unmatched closing bracket in multiline", async () => { - const stream = arrayToAsyncGen(["function test() {\n return 1;\n}\n}"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "", "", "test.ts", true) - const result = await collectAll(filtered) - // The chunk contains one complete function and one extra '}' - // Processing char by char: '{' at position 16 opens, '}' at position 32 closes (stack empty) - // '\n' at position 33, then '}' at position 34 is unmatched (stack empty) - // Yields chunk.slice(0, 34) which includes the newline after the first } - expect(result.join("")).toBe("function test() {\n return 1;\n}\n") - }) - - it("should handle multiple chunks in stream", async () => { - const stream = arrayToAsyncGen(["function", " test()", " {", "\n return", " 1;", "\n}"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "", "", "test.ts", true) - const result = await collectAll(filtered) - expect(result.join("")).toBe("function test() {\n return 1;\n}") - }) - }) - - describe("single-line mode", () => { - it("should allow completing brackets from current line", async () => { - const stream = arrayToAsyncGen(["x + 1)"]) - const filtered = service.stopOnUnmatchedClosingBracket( - stream, - "const result = calculate(", - ");", - "test.ts", - false, - ) - const result = await collectAll(filtered) - expect(result.join("")).toBe("x + 1)") - }) - - it("should handle bracket in suffix that gets overwritten", async () => { - const stream = arrayToAsyncGen(["1, 2, 3)"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "array.push(", ");", "test.ts", false) - const result = await collectAll(filtered) - // Should allow the closing paren because suffix has one - expect(result.join("")).toBe("1, 2, 3)") - }) - - it("should stop on unmatched closing bracket in single-line", async () => { - const stream = arrayToAsyncGen(["x + 1))"]) - const filtered = service.stopOnUnmatchedClosingBracket( - stream, - "const result = calculate(", - "", - "test.ts", - false, - ) - const result = await collectAll(filtered) - expect(result.join("")).toBe("x + 1)") - }) - - it("should handle multiple bracket pairs on current line", async () => { - const stream = arrayToAsyncGen(['" + y + ")])']) - const filtered = service.stopOnUnmatchedClosingBracket( - stream, - 'array.push({ key: getValue("x', - "", - "test.ts", - false, - ) - const result = await collectAll(filtered) - // Current line has: { ( ( - // Stream closes: ) ] ) - // First ) matches third (, second ] doesn't match second ( (expects }), stops before ] - expect(result.join("")).toBe('" + y + ")') - }) - }) - - describe("edge cases", () => { - it("should handle empty stream", async () => { - const stream = arrayToAsyncGen([]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "", "", "test.ts", true) - const result = await collectAll(filtered) - expect(result).toEqual([]) - }) - - it("should handle stream with only whitespace before brackets", async () => { - const stream = arrayToAsyncGen([" \n }"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "function test() {", "", "test.ts", true) - const result = await collectAll(filtered) - expect(result.join("")).toBe(" \n }") - }) - - it("should allow closing brackets before non-whitespace content", async () => { - const stream = arrayToAsyncGen([")\n const x = 1;"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "function test(", "", "test.ts", true) - const result = await collectAll(filtered) - // In multiline mode with no previous completion, stack starts empty but prefix has '(' - // Actually, prefix is NOT processed in multiline mode (only previous completion state) - // Whitespace section: searches for /[^\s\)\}\]]/, finds 'c' at index 7 - // Yields ')\n ' (everything before 'c'), then processes 'const x = 1;' - // 'const x = 1;' has no brackets, yields entire remaining chunk - expect(result.join("")).toBe(")\n const x = 1;") - }) - - it("should handle mixed bracket types correctly", async () => { - const stream = arrayToAsyncGen(["]})"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "obj = { arr: [{ key: val", "", "test.ts", true) - const result = await collectAll(filtered) - expect(result.join("")).toBe("]})") - }) - - it("should handle suffix with spaces before closing bracket", async () => { - const stream = arrayToAsyncGen(["1, 2, 3)"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "func(", " )", "test.ts", false) - const result = await collectAll(filtered) - // Spaces in suffix should be ignored, bracket should be added to stack - expect(result.join("")).toBe("1, 2, 3)") - }) + it("stops at an unmatched single-line closing bracket", async () => { + const result = service.stopOnUnmatchedClosingBracket( + stream(["x + 1))"]), + "const result = calculate(", + "", + "test.ts", + false, + ) + expect(await collect(result)).toBe("x + 1)") + }) - it("should stop when suffix parsing ends at non-bracket", async () => { - const stream = arrayToAsyncGen(["x)"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "func(", ") {", "test.ts", false) - const result = await collectAll(filtered) - // In single-line mode, current line is 'func() {' - // Stack from current line: ( opens, ) closes (matches), { opens -> stack = ['{'] - // Suffix ') {': unshift adds '(' to FRONT of stack -> stack = ['(', '{'] - // Stream 'x)': ')' is closing bracket - // stack.pop() removes from END, returns '{', BRACKETS['{'] = '}', char = ')' - // '}' !== ')' so condition is true, stops and yields 'x' - expect(result.join("")).toBe("x") - }) + it("tracks brackets opened by the current stream", async () => { + const result = service.stopOnUnmatchedClosingBracket( + stream(["function test() {", "\n return 1;", "\n}"]), + "", + "", + "test.ts", + true, + ) + expect(await collect(result)).toBe("function test() {\n return 1;\n}") + }) - it("should handle chunk boundary on closing bracket", async () => { - const stream = arrayToAsyncGen(["return 1", ";", "\n", "}", "extra"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "function test() {", "", "test.ts", true) - const result = await collectAll(filtered) - // In multiline mode without previous completion state, stack starts empty - // Prefix doesn't add to stack in multiline mode - // First chunk 'return 1' has no brackets, yielded - // Second chunk ';' has no brackets, yielded - // Third chunk '\n' is whitespace with no brackets, still in whitespace section - // Fourth chunk '}' is closing bracket in whitespace section, but stack is empty so stops immediately - expect(result.join("")).toBe("return 1;\n") - }) + it("stops at an unmatched multiline closing bracket", async () => { + const result = service.stopOnUnmatchedClosingBracket( + stream(["function test() {\n return 1;\n}\n}"]), + "", + "", + "test.ts", + true, + ) + expect(await collect(result)).toBe("function test() {\n return 1;\n}\n") + }) - it("should handle nested brackets in stream", async () => { - const stream = arrayToAsyncGen(["arr[i][j]"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "const val = ", ";", "test.ts", false) - const result = await collectAll(filtered) - expect(result.join("")).toBe("arr[i][j]") - }) + it("handles nested bracket types", async () => { + const result = service.stopOnUnmatchedClosingBracket(stream(["arr[i][j]"]), "const val = ", ";", "test.ts", false) + expect(await collect(result)).toBe("arr[i][j]") + }) - it("should handle unmatched opening brackets in stream", async () => { - const stream = arrayToAsyncGen(["arr[index"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "", "", "test.ts", true) - const result = await collectAll(filtered) - expect(result.join("")).toBe("arr[index") - }) - }) + it("uses closing brackets from a whitespace-prefixed suffix", async () => { + const result = service.stopOnUnmatchedClosingBracket(stream(["1, 2, 3)"]), "func(", " )", "test.ts", false) + expect(await collect(result)).toBe("1, 2, 3)") + }) - describe("state persistence across completions", () => { - it("should use state from previous completion in same file", async () => { - service.handleAcceptedCompletion("if (cond) {\n while (true) {", "test.ts") - const stream = arrayToAsyncGen(["\n doWork();\n }\n}"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "", "", "test.ts", true) - const result = await collectAll(filtered) - expect(result.join("")).toBe("\n doWork();\n }\n}") - }) + it("stops suffix bracket parsing at other content", async () => { + const result = service.stopOnUnmatchedClosingBracket(stream(["x)"]), "func(", ") {", "test.ts", false) + expect(await collect(result)).toBe("x") + }) - it("should not use state from previous file", async () => { - service.handleAcceptedCompletion("if (cond) {", "file1.ts") - const stream = arrayToAsyncGen(["}"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "", "", "file2.ts", true) - const result = await collectAll(filtered) - // Different file so stack is empty, but '}' is in whitespace section - // Since '}' doesn't match /[^\s\)\}\]]/, entire chunk yielded without bracket checking - expect(result.join("")).toBe("}") - }) + it("handles a closing bracket at a chunk boundary", async () => { + const result = service.stopOnUnmatchedClosingBracket( + stream(["return 1", ";", "\n", "}", "extra"]), + "function test() {", + "", + "test.ts", + true, + ) + expect(await collect(result)).toBe("return 1;\n") + }) - it("should clear state when switching files", async () => { - service.handleAcceptedCompletion("function a() {", "file1.ts") - service.handleAcceptedCompletion("function b() {", "file2.ts") - const stream = arrayToAsyncGen(["\n return;\n}"]) - const filtered = service.stopOnUnmatchedClosingBracket(stream, "", "", "file2.ts", true) - const result = await collectAll(filtered) - // Should only allow one closing brace from file2's state - expect(result.join("")).toBe("\n return;\n}") - }) - }) + it("handles an empty stream", async () => { + const result = service.stopOnUnmatchedClosingBracket(stream([]), "", "", "test.ts", true) + expect(await collect(result)).toBe("") }) }) diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.ts index 8ad73dda99..f46f287c1c 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.ts @@ -13,61 +13,27 @@ export const BRACKETS_REVERSE: { [key: string]: string } = { * But sometimes we started the pair in a previous autocomplete suggestion */ export class BracketMatchingService { - private openingBracketsFromLastCompletion: string[] = [] - private lastCompletionFile: string | undefined = undefined - - handleAcceptedCompletion(completion: string, filepath: string) { - this.openingBracketsFromLastCompletion = [] - const stack: string[] = [] - - for (let i = 0; i < completion.length; i++) { - const char = completion[i] - if (Object.keys(BRACKETS).includes(char)) { - // It's an opening bracket - stack.push(char) - } else if (Object.values(BRACKETS).includes(char)) { - // It's a closing bracket - if (stack.length === 0 || BRACKETS[stack.pop()!] !== char) { - break - } - } - } - - // Any remaining opening brackets in the stack are uncompleted - this.openingBracketsFromLastCompletion = stack - this.lastCompletionFile = filepath - } - async *stopOnUnmatchedClosingBracket( stream: AsyncGenerator, prefix: string, suffix: string, - filepath: string, + _filepath: string, multiline: boolean, // Whether this is a multiline completion or not ): AsyncGenerator { - let stack: string[] = [] - if (multiline) { - // Add opening brackets from the previous response - if (this.lastCompletionFile === filepath) { - stack = [...this.openingBracketsFromLastCompletion] - } else { - this.lastCompletionFile = undefined - } - } else { + const stack: string[] = [] + if (!multiline) { // If single line completion, then allow completing bracket pairs that are // started on the current line but not finished on the current line - if (!multiline) { - const currentLine = (prefix.split("\n").pop() ?? "") + (suffix.split("\n")[0] ?? "") - for (let i = 0; i < currentLine.length; i++) { - const char = currentLine[i] - if (Object.keys(BRACKETS).includes(char)) { - // It's an opening bracket - stack.push(char) - } else if (Object.values(BRACKETS).includes(char)) { - // It's a closing bracket - if (stack.length === 0 || BRACKETS[stack.pop()!] !== char) { - break - } + const currentLine = (prefix.split("\n").pop() ?? "") + (suffix.split("\n")[0] ?? "") + for (let i = 0; i < currentLine.length; i++) { + const char = currentLine[i] + if (Object.keys(BRACKETS).includes(char)) { + // It's an opening bracket + stack.push(char) + } else if (Object.values(BRACKETS).includes(char)) { + // It's a closing bracket + if (stack.length === 0 || BRACKETS[stack.pop()!] !== char) { + break } } } diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.test.ts deleted file mode 100644 index c89b6bddf3..0000000000 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { beforeEach, describe, expect, it, Mock, vi } from "vitest" - -import { - avoidPathLine, - avoidEmptyComments, - streamWithNewLines, - lineIsRepeated, - stopAtSimilarLine, - stopAtLines, - LINES_TO_STOP_AT, - PREFIXES_TO_SKIP, - skipPrefixes, - stopAtRepeatingLines, -} from "./lineStream" - -describe("lineStream (production-used subset)", () => { - let mockFullStop: Mock - - async function getLineGenerator(lines: any[]) { - return (async function* () { - for (const line of lines) { - yield line - } - })() - } - - async function getFilteredLines(results: AsyncGenerator) { - const output: string[] = [] - for await (const line of results) { - output.push(line) - } - return output - } - - beforeEach(() => { - mockFullStop = vi.fn() - }) - - describe("avoidPathLine", () => { - it("filters out '// Path: ...' lines", async () => { - const linesGenerator = await getLineGenerator(["// Path: src/index.ts", "const x = 5;", "//", "console.log(x);"]) - const result = avoidPathLine(linesGenerator, "//") - const filteredLines = await getFilteredLines(result) - expect(filteredLines).toEqual(["const x = 5;", "//", "console.log(x);"]) - }) - }) - - describe("avoidEmptyComments", () => { - it("filters out empty comment-only lines", async () => { - const linesGenerator = await getLineGenerator(["// Path: src/index.ts", "const x = 5;", "//", "console.log(x);"]) - const result = avoidEmptyComments(linesGenerator, "//") - const filteredLines = await getFilteredLines(result) - expect(filteredLines).toEqual(["// Path: src/index.ts", "const x = 5;", "console.log(x);"]) - }) - }) - - describe("streamWithNewLines", () => { - it("adds newline separators between lines", async () => { - const linesGenerator = await getLineGenerator(["line1", "line2", "line3"]) - const result = streamWithNewLines(linesGenerator) - const filteredLines = await getFilteredLines(result) - expect(filteredLines).toEqual(["line1", "\n", "line2", "\n", "line3"]) - }) - }) - - describe("lineIsRepeated", () => { - it("returns true for similar lines", () => { - expect(lineIsRepeated("const x = 5;", "const x = 6;")).toBe(true) - }) - - it("returns false for different lines", () => { - expect(lineIsRepeated("const x = 5;", "let y = 10;")).toBe(false) - }) - - it("returns false for short lines", () => { - expect(lineIsRepeated("x=5", "x=6")).toBe(false) - }) - }) - - describe("stopAtSimilarLine", () => { - it("stops at the exact same line", async () => { - const lineToTest = "const x = 6" - const linesGenerator = await getLineGenerator(["console.log();", "const y = () => {};", lineToTest]) - - const result = stopAtSimilarLine(linesGenerator, lineToTest, mockFullStop) - const filteredLines = await getFilteredLines(result) - - expect(filteredLines).toEqual(["console.log();", "const y = () => {};"]) - expect(mockFullStop).toHaveBeenCalledTimes(1) - }) - - it("stops at a similar line", async () => { - const lineToTest = "const x = 6;" - const linesGenerator = await getLineGenerator(["console.log();", "const y = () => {};", lineToTest]) - - const result = stopAtSimilarLine(linesGenerator, "a" + lineToTest, mockFullStop) - const filteredLines = await getFilteredLines(result) - - expect(filteredLines).toEqual(["console.log();", "const y = () => {};"]) - expect(mockFullStop).toHaveBeenCalledTimes(1) - }) - - it("continues on bracket-ending lines", async () => { - const linesGenerator = await getLineGenerator([" if (x > 0) {", " console.log(x);", " }"]) - - const result = stopAtSimilarLine(linesGenerator, "}", mockFullStop) - const filteredLines = await getFilteredLines(result) - - expect(filteredLines).toEqual([" if (x > 0) {", " console.log(x);", " }"]) - expect(mockFullStop).toHaveBeenCalledTimes(0) - }) - }) - - describe("stopAtLines", () => { - it("stops at specified lines", async () => { - const linesGenerator = await getLineGenerator([ - "const x = 5;", - "let y = 10;", - LINES_TO_STOP_AT[0], - "const z = 15;", - ]) - - const result = stopAtLines(linesGenerator, mockFullStop) - const filteredLines = await getFilteredLines(result) - - expect(filteredLines).toEqual(["const x = 5;", "let y = 10;"]) - expect(mockFullStop).toHaveBeenCalledTimes(1) - }) - - it("stops when stop phrase has leading whitespace", async () => { - const linesGenerator = await getLineGenerator([ - "const x = 5;", - "let y = 10;", - ` ${LINES_TO_STOP_AT[0]}`, - "const z = 15;", - ]) - - const result = stopAtLines(linesGenerator, mockFullStop) - const filteredLines = await getFilteredLines(result) - - expect(filteredLines).toEqual(["const x = 5;", "let y = 10;"]) - expect(mockFullStop).toHaveBeenCalledTimes(1) - }) - }) - - describe("skipPrefixes", () => { - it("skips configured prefixes on the first line", async () => { - const linesGenerator = await getLineGenerator([`${PREFIXES_TO_SKIP[0]}const x = 5;`, "let y = 10;"]) - - const result = skipPrefixes(linesGenerator) - const filteredLines = await getFilteredLines(result) - - expect(filteredLines).toEqual(["const x = 5;", "let y = 10;"]) - }) - }) - - describe("stopAtRepeatingLines", () => { - it("yields non-repeating lines and does not stop prematurely", async () => { - const linesGenerator = await getLineGenerator(["a", "b", "c", "d", "e"]) - - const result = stopAtRepeatingLines(linesGenerator as any, mockFullStop) - const filteredLines = await getFilteredLines(result as any) - - expect(filteredLines).toEqual(["a", "b", "c", "d", "e"]) - expect(mockFullStop).not.toHaveBeenCalled() - }) - - it("stops when a line repeats 3 times consecutively", async () => { - const linesGenerator = await getLineGenerator(["x", "x", "x", "x", "after"]) - - const result = stopAtRepeatingLines(linesGenerator as any, mockFullStop) - const filteredLines = await getFilteredLines(result as any) - - // Only the first of the repeating lines is yielded - expect(filteredLines).toEqual(["x"]) - expect(mockFullStop).toHaveBeenCalledTimes(1) - }) - }) -}) diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.ts deleted file mode 100644 index a4cc5e1526..0000000000 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { LineStream } from "../../../diff/util" -import { lineIsRepeated } from "../../util/textSimilarity" - -export { lineIsRepeated } - -export type LineFilter = (args: { lines: LineStream; fullStop: () => void }) => LineStream - -export type CharacterFilter = (args: { - chars: AsyncGenerator - prefix: string - suffix: string - filepath: string - multiline: boolean -}) => AsyncGenerator - -const BRACKET_ENDING_CHARS = [")", "]", "}", ";"] -export const PREFIXES_TO_SKIP = [""] -export const LINES_TO_STOP_AT = ["# End of file.", "", "```"] - -function isBracketEnding(line: string): boolean { - return line - .trim() - .split("") - .some((char) => BRACKET_ENDING_CHARS.includes(char)) -} - -/** - * Validate whether a stop pattern in a line is in a valid context (not inside quotes or identifiers) - * and capture the text before the pattern. - * Internal helper for stopAtLines. - */ -function validatePatternInLine( - line: string, - pattern: string, -): { - isValid: boolean - patternIndex: number - beforePattern: string -} { - const patternIndex = line.indexOf(pattern) - - if (patternIndex === -1) { - return { isValid: false, patternIndex: -1, beforePattern: "" } - } - - // If preceded by a non-whitespace, treat as part of an identifier - if (patternIndex > 0) { - const charBefore = line[patternIndex - 1] - if (charBefore && !charBefore.match(/\s/)) { - return { isValid: false, patternIndex, beforePattern: "" } - } - } - - const beforePattern = line.substring(0, patternIndex) - const singleQuotes = (beforePattern.match(/'/g) || []).length - const doubleQuotes = (beforePattern.match(/"/g) || []).length - - // Odd number of quotes before the pattern - likely inside quotes - if (singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0) { - return { isValid: false, patternIndex, beforePattern } - } - - return { isValid: true, patternIndex, beforePattern } -} - -/** - * Filter out lines starting with "// Path: " which models sometimes echo. - */ -export async function* avoidPathLine(stream: LineStream, comment?: string): LineStream { - for await (const line of stream) { - if (comment && line.startsWith(`${comment} Path: `)) { - continue - } - yield line - } -} - -/** - * Filter out empty comment-only lines. - */ -export async function* avoidEmptyComments(stream: LineStream, comment?: string): LineStream { - for await (const line of stream) { - if (!comment || line.trim() !== comment) { - yield line - } - } -} - -/** - * Insert "\n" separators between streamed lines. - */ -export async function* streamWithNewLines(stream: LineStream): LineStream { - let firstLine = true - for await (const nextLine of stream) { - if (!firstLine) { - yield "\n" - } - firstLine = false - yield nextLine - } -} - -/** - * Yield until a line equals or is very similar to the provided line, then call fullStop. - * If the provided line ends with a bracket/semicolon, allow exact trimmed matches to pass through. - */ -export async function* stopAtSimilarLine( - stream: LineStream, - line: string, - fullStop: () => void, -): AsyncGenerator { - const trimmedLine = line.trim() - const lineIsBracketEnding = isBracketEnding(trimmedLine) - - for await (const nextLine of stream) { - if (trimmedLine === "") { - yield nextLine - continue - } - - if (lineIsBracketEnding && trimmedLine === nextLine.trim()) { - yield nextLine - continue - } - - if (nextLine === line) { - fullStop() - break - } - - if (lineIsRepeated(nextLine, trimmedLine)) { - fullStop() - break - } - - yield nextLine - } -} - -/** - * Yield until any of the stop phrases is encountered in a valid context, then call fullStop. - */ -export async function* stopAtLines( - stream: LineStream, - fullStop: () => void, - linesToStopAt: string[] = LINES_TO_STOP_AT, -): LineStream { - for await (const line of stream) { - let shouldStop = false - - for (const stopAt of linesToStopAt) { - if (line.includes(stopAt)) { - const validation = validatePatternInLine(line, stopAt) - if (!validation.isValid) { - continue - } - - const trimmedLine = line.trimStart() - if (trimmedLine.startsWith(stopAt)) { - shouldStop = true - break - } else { - const contentBeforeStopPhrase = validation.beforePattern.trimEnd() - if (contentBeforeStopPhrase.length < validation.beforePattern.length) { - shouldStop = true - break - } - } - } - } - - if (shouldStop) { - fullStop() - break - } - yield line - } -} - -/** - * On the first line only, strip any configured prefix (e.g. ""). - */ -export async function* skipPrefixes(lines: LineStream): LineStream { - let isFirstLine = true - for await (const line of lines) { - if (isFirstLine) { - const match = PREFIXES_TO_SKIP.find((prefix) => line.startsWith(prefix)) - if (match) { - yield line.slice(match.length) - continue - } - isFirstLine = false - } - yield line - } -} - -/** - * Yield lines until a line repeats 3 times consecutively. Only the first of the repeats is yielded. - */ -export async function* stopAtRepeatingLines(lines: LineStream, fullStop: () => void): LineStream { - let previousLine: string | undefined - let repeatCount = 0 - const MAX_REPEATS = 3 - - for await (const line of lines) { - if (line === previousLine) { - repeatCount++ - if (repeatCount === MAX_REPEATS) { - fullStop() - return - } - } else { - yield line - repeatCount = 1 - } - previousLine = line - } -} diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.test.ts index c47bcf7661..93abefa3cd 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.test.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.test.ts @@ -1,450 +1,111 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" -import { getAllSnippets, getAllSnippetsWithoutRace } from "./getAllSnippets" +import { beforeEach, describe, expect, it, vi } from "vitest" +import type { ContextRetrievalService } from "../context/ContextRetrievalService" import { AutocompleteSnippetType } from "../types" import type { HelperVars } from "../util/HelperVars" import type { IDE } from "../../index" -import type { GetLspDefinitionsFunction } from "../types" -import type { ContextRetrievalService } from "../context/ContextRetrievalService" +import { getAllSnippetsWithoutRace } from "./getAllSnippets" -describe("getAllSnippets", () => { - let mockHelper: HelperVars - let mockIde: IDE - let mockGetDefinitionsFromLsp: GetLspDefinitionsFunction - let mockContextRetrievalService: ContextRetrievalService +describe("getAllSnippetsWithoutRace", () => { + let helper: HelperVars + let ide: IDE + let context: ContextRetrievalService beforeEach(() => { - // Create mock helper with minimal required properties - mockHelper = { + helper = { input: { filepath: "/test/file.ts", - recentlyEditedRanges: [ - { - filepath: "/test/recent.ts", - lines: ["const x = 1;", "const y = 2;"], - }, - ], + recentlyEditedRanges: [{ filepath: "/test/recent.ts", lines: ["const x = 1;"] }], recentlyVisitedRanges: [ - { - filepath: "/test/visited.ts", - content: "visited content", - type: AutocompleteSnippetType.Code, - }, + { filepath: "/test/visited.ts", content: "visited", type: AutocompleteSnippetType.Code }, ], }, filepath: "/test/file.ts", - fullPrefix: "const result = ", - fullSuffix: ";", - lang: "typescript", options: { - onlyMyCode: false, useRecentlyEdited: true, useRecentlyOpened: true, experimental_enableStaticContextualization: false, }, - } as any - - // Create mock IDE - mockIde = { - getWorkspaceDirs: vi.fn().mockResolvedValue(["/test"]), - getClipboardContent: vi.fn().mockResolvedValue({ - text: "clipboard content", - copiedAt: "2024-01-01T00:00:00.000Z", - }), + } as HelperVars + ide = { + getClipboardContent: vi.fn().mockResolvedValue({ text: "clipboard", copiedAt: "2024-01-01" }), readFile: vi.fn().mockResolvedValue("file content"), - } as any - - // Create mock LSP function - mockGetDefinitionsFromLsp = vi.fn().mockResolvedValue([]) - - // Create mock context retrieval service - mockContextRetrievalService = { + } as unknown as IDE + context = { getRootPathSnippets: vi.fn().mockResolvedValue([]), getSnippetsFromImportDefinitions: vi.fn().mockResolvedValue([]), getStaticContextSnippets: vi.fn().mockResolvedValue([]), - } as any - }) - - afterEach(() => { - vi.clearAllMocks() + } as unknown as ContextRetrievalService }) - describe("getAllSnippets with race conditions", () => { - it("should return all snippet types", async () => { - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result).toHaveProperty("rootPathSnippets") - expect(result).toHaveProperty("importDefinitionSnippets") - expect(result).toHaveProperty("ideSnippets") - expect(result).toHaveProperty("recentlyEditedRangeSnippets") - expect(result).toHaveProperty("diffSnippets") - expect(result).toHaveProperty("clipboardSnippets") - expect(result).toHaveProperty("recentlyVisitedRangesSnippets") - expect(result).toHaveProperty("recentlyOpenedFileSnippets") - expect(result).toHaveProperty("staticSnippet") - }) - - it("should collect recently edited snippets synchronously", async () => { - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.recentlyEditedRangeSnippets).toHaveLength(1) - expect(result.recentlyEditedRangeSnippets[0]).toEqual({ - filepath: "/test/recent.ts", - content: "const x = 1;\nconst y = 2;", - type: AutocompleteSnippetType.Code, - }) - }) - - it("should pass through recently visited ranges", async () => { - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.recentlyVisitedRangesSnippets).toEqual(mockHelper.input.recentlyVisitedRanges) - }) - - it("should timeout slow snippet sources after default 100ms", async () => { - // Mock a slow service that takes 200ms - mockContextRetrievalService.getRootPathSnippets = vi.fn().mockImplementation( - () => - new Promise((resolve) => { - setTimeout( - () => - resolve([ - { - filepath: "/slow.ts", - content: "slow", - type: AutocompleteSnippetType.Code, - }, - ]), - 200, - ) - }), - ) - - const startTime = Date.now() - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - const duration = Date.now() - startTime - - // Should timeout and return empty array, not wait 200ms - expect(result.rootPathSnippets).toEqual([]) - expect(duration).toBeLessThan(150) // Some buffer for timing - }) - - it("should return results from fast sources even if other sources are slow", async () => { - // Mock one fast and one slow source - mockContextRetrievalService.getRootPathSnippets = vi.fn().mockResolvedValue([ - { - filepath: "/fast.ts", - content: "fast", - type: AutocompleteSnippetType.Code, - }, - ]) - mockContextRetrievalService.getSnippetsFromImportDefinitions = vi - .fn() - .mockImplementation(() => new Promise((resolve) => setTimeout(() => resolve([]), 200))) - - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - // Fast source should return results - expect(result.rootPathSnippets).toHaveLength(1) - expect(result.rootPathSnippets[0].content).toBe("fast") - - // Slow source should timeout and return empty - expect(result.importDefinitionSnippets).toEqual([]) - }) - - it("should collect clipboard snippets", async () => { - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.clipboardSnippets).toHaveLength(1) - expect(result.clipboardSnippets[0]).toEqual({ - content: "clipboard content", - copiedAt: "2024-01-01T00:00:00.000Z", - type: AutocompleteSnippetType.Clipboard, - }) - }) - - it("should handle empty results from snippet sources", async () => { - // All sources return empty - mockContextRetrievalService.getRootPathSnippets = vi.fn().mockResolvedValue([]) - mockContextRetrievalService.getSnippetsFromImportDefinitions = vi.fn().mockResolvedValue([]) - mockIde.getClipboardContent = vi.fn().mockResolvedValue({ - text: "", - copiedAt: "2024-01-01T00:00:00.000Z", - }) - - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.rootPathSnippets).toEqual([]) - expect(result.importDefinitionSnippets).toEqual([]) - expect(result.clipboardSnippets).toHaveLength(1) // Still returns clipboard snippet - }) - - it("should return empty array for IDE snippets when disabled", async () => { - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - // IDE_SNIPPETS_ENABLED is false in the implementation - expect(result.ideSnippets).toEqual([]) - expect(mockGetDefinitionsFromLsp).not.toHaveBeenCalled() - }) - - it("should return empty array for diff snippets (temporarily disabled)", async () => { - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.diffSnippets).toEqual([]) - }) - - it("should handle option useRecentlyEdited = false", async () => { - mockHelper.options.useRecentlyEdited = false - - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.recentlyEditedRangeSnippets).toEqual([]) - }) - - it("should handle option useRecentlyOpened = false", async () => { - mockHelper.options.useRecentlyOpened = false - - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.recentlyOpenedFileSnippets).toEqual([]) - }) - - it("should collect static context snippets when experimental flag is enabled", async () => { - mockHelper.options.experimental_enableStaticContextualization = true - mockContextRetrievalService.getStaticContextSnippets = vi.fn().mockResolvedValue([ - { - filepath: "/static.ts", - content: "static", - type: AutocompleteSnippetType.Static, - }, - ]) - - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.staticSnippet).toHaveLength(1) - expect(result.staticSnippet[0].content).toBe("static") - }) - - it("should return empty static snippets when experimental flag is disabled", async () => { - mockHelper.options.experimental_enableStaticContextualization = false - - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result.staticSnippet).toEqual([]) - expect(mockContextRetrievalService.getStaticContextSnippets).not.toHaveBeenCalled() + it("collects every active snippet source", async () => { + const result = await getAllSnippetsWithoutRace({ helper, ide, contextRetrievalService: context }) + + expect(result).toEqual({ + rootPathSnippets: [], + importDefinitionSnippets: [], + recentlyEditedRangeSnippets: [ + { filepath: "/test/recent.ts", content: "const x = 1;", type: AutocompleteSnippetType.Code }, + ], + recentlyVisitedRangesSnippets: [ + { filepath: "/test/visited.ts", content: "visited", type: AutocompleteSnippetType.Code }, + ], + clipboardSnippets: [{ content: "clipboard", copiedAt: "2024-01-01", type: AutocompleteSnippetType.Clipboard }], + recentlyOpenedFileSnippets: [], + staticSnippet: [], }) }) - describe("error handling", () => { - it("should propagate errors from context retrieval service", async () => { - mockContextRetrievalService.getRootPathSnippets = vi.fn().mockRejectedValue(new Error("Service error")) - - // Errors are not caught by racePromise - they propagate if they occur before timeout - await expect( - getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }), - ).rejects.toThrow("Service error") - }) - - it("should propagate errors from IDE clipboard", async () => { - mockIde.getClipboardContent = vi.fn().mockRejectedValue(new Error("Clipboard error")) - - // Errors are not caught by racePromise - they propagate if they occur before timeout - await expect( - getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }), - ).rejects.toThrow("Clipboard error") - }) - - it("should pass through null from snippet sources", async () => { - mockContextRetrievalService.getRootPathSnippets = vi.fn().mockResolvedValue(null as any) + it("honors disabled recent-context sources", async () => { + helper.options.useRecentlyEdited = false + helper.options.useRecentlyOpened = false - const result = await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - // racePromise returns null if the promise resolves to null (not converted to []) - expect(result.rootPathSnippets).toBeNull() - }) + const result = await getAllSnippetsWithoutRace({ helper, ide, contextRetrievalService: context }) + expect(result.recentlyEditedRangeSnippets).toEqual([]) + expect(result.recentlyOpenedFileSnippets).toEqual([]) }) - describe("getAllSnippetsWithoutRace", () => { - it("should wait for all promises without timeout", async () => { - // Mock a slow service that takes 200ms - mockContextRetrievalService.getRootPathSnippets = vi.fn().mockImplementation( - () => - new Promise((resolve) => { - setTimeout( - () => - resolve([ - { - filepath: "/slow.ts", - content: "slow", - type: AutocompleteSnippetType.Code, - }, - ]), - 200, - ) - }), - ) - - const result = await getAllSnippetsWithoutRace({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - // Should wait and get results, not timeout - expect(result.rootPathSnippets).toHaveLength(1) - expect(result.rootPathSnippets[0].content).toBe("slow") - }) - - it("should return all snippet types without racing", async () => { - const result = await getAllSnippetsWithoutRace({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) - - expect(result).toHaveProperty("rootPathSnippets") - expect(result).toHaveProperty("importDefinitionSnippets") - expect(result).toHaveProperty("ideSnippets") - expect(result).toHaveProperty("recentlyEditedRangeSnippets") - expect(result).toHaveProperty("diffSnippets") - expect(result).toHaveProperty("clipboardSnippets") - expect(result).toHaveProperty("recentlyVisitedRangesSnippets") - expect(result).toHaveProperty("recentlyOpenedFileSnippets") - expect(result).toHaveProperty("staticSnippet") - }) - - it("should handle errors without race timeout", async () => { - mockContextRetrievalService.getRootPathSnippets = vi.fn().mockRejectedValue(new Error("Service error")) - - // Should propagate error since no timeout - await expect( - getAllSnippetsWithoutRace({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }), - ).rejects.toThrow("Service error") - }) + it("collects import definitions and enabled static context", async () => { + helper.options.experimental_enableStaticContextualization = true + context.getSnippetsFromImportDefinitions = vi + .fn() + .mockResolvedValue([{ filepath: "/import.ts", content: "imported", type: AutocompleteSnippetType.Code }]) + context.getStaticContextSnippets = vi + .fn() + .mockResolvedValue([{ filepath: "/static.ts", content: "static", type: AutocompleteSnippetType.Static }]) + + const result = await getAllSnippetsWithoutRace({ helper, ide, contextRetrievalService: context }) + expect(result.importDefinitionSnippets[0]?.content).toBe("imported") + expect(result.staticSnippet[0]?.content).toBe("static") }) - describe("parallel execution", () => { - it("should execute all snippet collections in parallel", async () => { - const executionOrder: string[] = [] + it("propagates clipboard failures", async () => { + ide.getClipboardContent = vi.fn().mockRejectedValue(new Error("Clipboard error")) - mockContextRetrievalService.getRootPathSnippets = vi.fn().mockImplementation(async () => { - executionOrder.push("rootPath-start") - await new Promise((resolve) => setTimeout(resolve, 50)) - executionOrder.push("rootPath-end") - return [] - }) + await expect(getAllSnippetsWithoutRace({ helper, ide, contextRetrievalService: context })).rejects.toThrow( + "Clipboard error", + ) + }) - mockContextRetrievalService.getSnippetsFromImportDefinitions = vi.fn().mockImplementation(async () => { - executionOrder.push("import-start") - await new Promise((resolve) => setTimeout(resolve, 50)) - executionOrder.push("import-end") - return [] - }) + it("waits for active context sources without a collector timeout", async () => { + context.getRootPathSnippets = vi.fn().mockImplementation( + () => + new Promise((resolve) => { + setTimeout( + () => resolve([{ filepath: "/slow.ts", content: "slow", type: AutocompleteSnippetType.Code }]), + 120, + ) + }), + ) - mockIde.getClipboardContent = vi.fn().mockImplementation(async () => { - executionOrder.push("clipboard-start") - await new Promise((resolve) => setTimeout(resolve, 50)) - executionOrder.push("clipboard-end") - return { text: "test", copiedAt: "2024-01-01T00:00:00.000Z" } - }) + const result = await getAllSnippetsWithoutRace({ helper, ide, contextRetrievalService: context }) + expect(result.rootPathSnippets[0]?.content).toBe("slow") + }) - await getAllSnippets({ - helper: mockHelper, - ide: mockIde, - getDefinitionsFromLsp: mockGetDefinitionsFromLsp, - contextRetrievalService: mockContextRetrievalService, - }) + it("propagates context retrieval failures", async () => { + context.getRootPathSnippets = vi.fn().mockRejectedValue(new Error("Service error")) - // All should start before any complete (parallel execution) - expect(executionOrder[0]).toBe("rootPath-start") - expect(executionOrder[1]).toBe("import-start") - expect(executionOrder[2]).toBe("clipboard-start") - }) + await expect(getAllSnippetsWithoutRace({ helper, ide, contextRetrievalService: context })).rejects.toThrow( + "Service error", + ) }) }) diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.ts index b2adc447fb..be208b4f93 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.ts @@ -1,215 +1,89 @@ import { IDE } from "../../index" -import { findUriInDirs } from "../../util/uri" import { ContextRetrievalService } from "../context/ContextRetrievalService" -import { GetLspDefinitionsFunction } from "../types" import { HelperVars } from "../util/HelperVars" import { openedFilesLruCache } from "../util/openedFilesLruCache" - import { AutocompleteClipboardSnippet, AutocompleteCodeSnippet, - AutocompleteDiffSnippet, AutocompleteSnippetType, AutocompleteStaticSnippet, } from "../types" -const IDE_SNIPPETS_ENABLED = false // ideSnippets is not used, so it's temporarily disabled - export interface SnippetPayload { rootPathSnippets: AutocompleteCodeSnippet[] importDefinitionSnippets: AutocompleteCodeSnippet[] - ideSnippets: AutocompleteCodeSnippet[] recentlyEditedRangeSnippets: AutocompleteCodeSnippet[] recentlyVisitedRangesSnippets: AutocompleteCodeSnippet[] - diffSnippets: AutocompleteDiffSnippet[] clipboardSnippets: AutocompleteClipboardSnippet[] recentlyOpenedFileSnippets: AutocompleteCodeSnippet[] staticSnippet: AutocompleteStaticSnippet[] } -function racePromise(promise: Promise, timeout = 100): Promise { - const timeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve([]), timeout) - }) - - return Promise.race([promise, timeoutPromise]) -} - -// Some IDEs might have special ways of finding snippets (e.g. JetBrains and VS Code have different "LSP-equivalent" systems, -// or they might separately track recently edited ranges) -async function getIdeSnippets( - helper: HelperVars, - ide: IDE, - getDefinitionsFromLsp: GetLspDefinitionsFunction, -): Promise { - const ideSnippets = await getDefinitionsFromLsp( - helper.input.filepath, - helper.fullPrefix + helper.fullSuffix, - helper.fullPrefix.length, - ide, - helper.lang, - ) - - if (helper.options.onlyMyCode) { - const workspaceDirs = await ide.getWorkspaceDirs() - - return ideSnippets.filter((snippet) => - workspaceDirs.some((dir) => !!findUriInDirs(snippet.filepath, [dir]).foundInDir), - ) - } - - return ideSnippets -} - function getSnippetsFromRecentlyEditedRanges(helper: HelperVars): AutocompleteCodeSnippet[] { - if (helper.options.useRecentlyEdited === false) { - return [] - } + if (helper.options.useRecentlyEdited === false) return [] - return helper.input.recentlyEditedRanges.map((range) => { - return { - filepath: range.filepath, - content: range.lines.join("\n"), - type: AutocompleteSnippetType.Code, - } - }) + return helper.input.recentlyEditedRanges.map((range) => ({ + filepath: range.filepath, + content: range.lines.join("\n"), + type: AutocompleteSnippetType.Code, + })) } const getClipboardSnippets = async (ide: IDE): Promise => { const content = await ide.getClipboardContent() - - return [content].map((item) => { - return { - content: item.text, - copiedAt: item.copiedAt, + return [ + { + content: content.text, + copiedAt: content.copiedAt, type: AutocompleteSnippetType.Clipboard, - } - }) + }, + ] } const getSnippetsFromRecentlyOpenedFiles = async (helper: HelperVars, ide: IDE): Promise => { - if (helper.options.useRecentlyOpened === false) { - return [] - } + if (helper.options.useRecentlyOpened === false) return [] try { - const currentFileUri = `${helper.filepath}` - - // Get all file URIs excluding the current file - const fileUrisToRead = [...openedFilesLruCache.entriesDescending()] - .filter(([fileUri, _]) => fileUri !== currentFileUri) - .map(([fileUri, _]) => fileUri) - - // Create an array of promises that each read a file with timeout - const fileReadPromises = fileUrisToRead.map((fileUri) => { - // Create a promise that resolves to a snippet or null - const readPromise = new Promise((resolve) => { + const current = `${helper.filepath}` + const uris = [...openedFilesLruCache.entriesDescending()].filter(([uri]) => uri !== current).map(([uri]) => uri) + const reads = uris.map((uri) => { + const read = new Promise((resolve) => { ide - .readFile(fileUri) - .then((fileContent) => { - if (!fileContent || fileContent.trim() === "") { + .readFile(uri) + .then((content) => { + if (!content || content.trim() === "") { resolve(null) return } - - resolve({ - filepath: fileUri, - content: fileContent, - type: AutocompleteSnippetType.Code, - }) + resolve({ filepath: uri, content, type: AutocompleteSnippetType.Code }) }) - .catch((e) => { - console.error(`Failed to read file ${fileUri}:`, e) + .catch((err) => { + console.error(`Failed to read file ${uri}:`, err) resolve(null) }) }) - // Cut off at 80ms via racing promises - return Promise.race([readPromise, new Promise((resolve) => setTimeout(() => resolve(null), 80))]) + return Promise.race([read, new Promise((resolve) => setTimeout(() => resolve(null), 80))]) }) - - // Execute all file reads in parallel - const results = await Promise.all(fileReadPromises) - - // Filter out null results + const results = await Promise.all(reads) return results.filter(Boolean) as AutocompleteCodeSnippet[] - } catch (e) { - console.error("Error processing opened files cache:", e) + } catch (err) { + console.error("Error processing opened files cache:", err) return [] } } -export const getAllSnippets = async ({ - helper, - ide, - getDefinitionsFromLsp, - contextRetrievalService, -}: { - helper: HelperVars - ide: IDE - getDefinitionsFromLsp: GetLspDefinitionsFunction - contextRetrievalService: ContextRetrievalService -}): Promise => { - const recentlyEditedRangeSnippets = getSnippetsFromRecentlyEditedRanges(helper) - - const [ - rootPathSnippets, - importDefinitionSnippets, - ideSnippets, - diffSnippets, - clipboardSnippets, - recentlyOpenedFileSnippets, - staticSnippet, - ] = await Promise.all([ - racePromise(contextRetrievalService.getRootPathSnippets(helper)), - racePromise(contextRetrievalService.getSnippetsFromImportDefinitions(helper)), - IDE_SNIPPETS_ENABLED ? racePromise(getIdeSnippets(helper, ide, getDefinitionsFromLsp)) : [], - [], // racePromise(getDiffSnippets(ide)) // temporarily disabled, see https://github.com/continuedev/continue/pull/5882, - racePromise(getClipboardSnippets(ide)), - racePromise(getSnippetsFromRecentlyOpenedFiles(helper, ide)), // giving this one a little more time to complete - helper.options.experimental_enableStaticContextualization - ? racePromise(contextRetrievalService.getStaticContextSnippets(helper)) - : [], - ]) - - return { - rootPathSnippets, - importDefinitionSnippets, - ideSnippets, - recentlyEditedRangeSnippets, - diffSnippets, - clipboardSnippets, - recentlyVisitedRangesSnippets: helper.input.recentlyVisitedRanges, - recentlyOpenedFileSnippets, - staticSnippet, - } -} - export const getAllSnippetsWithoutRace = async ({ helper, ide, - getDefinitionsFromLsp, contextRetrievalService, }: { helper: HelperVars ide: IDE - getDefinitionsFromLsp: GetLspDefinitionsFunction contextRetrievalService: ContextRetrievalService }): Promise => { - const recentlyEditedRangeSnippets = getSnippetsFromRecentlyEditedRanges(helper) - - const [ - rootPathSnippets, - importDefinitionSnippets, - ideSnippets, - diffSnippets, - clipboardSnippets, - recentlyOpenedFileSnippets, - staticSnippet, - ] = await Promise.all([ + const [root, imports, clipboard, opened, staticSnippet] = await Promise.all([ contextRetrievalService.getRootPathSnippets(helper), contextRetrievalService.getSnippetsFromImportDefinitions(helper), - IDE_SNIPPETS_ENABLED ? getIdeSnippets(helper, ide, getDefinitionsFromLsp) : [], - [], // racePromise(getDiffSnippets(ide)) // temporarily disabled, see https://github.com/continuedev/continue/pull/5882, getClipboardSnippets(ide), getSnippetsFromRecentlyOpenedFiles(helper, ide), helper.options.experimental_enableStaticContextualization @@ -218,14 +92,12 @@ export const getAllSnippetsWithoutRace = async ({ ]) return { - rootPathSnippets, - importDefinitionSnippets, - ideSnippets, - recentlyEditedRangeSnippets, - diffSnippets, - clipboardSnippets, + rootPathSnippets: root, + importDefinitionSnippets: imports, + recentlyEditedRangeSnippets: getSnippetsFromRecentlyEditedRanges(helper), recentlyVisitedRangesSnippets: helper.input.recentlyVisitedRanges, - recentlyOpenedFileSnippets, + clipboardSnippets: clipboard, + recentlyOpenedFileSnippets: opened, staticSnippet, } } diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/AutocompleteTemplate.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/AutocompleteTemplate.ts index 229fdbf5e3..4904928e4a 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/AutocompleteTemplate.ts @@ -5,7 +5,7 @@ import { CompletionOptions } from "../../index.js" import { getLastNUriRelativePathParts, getShortestUniqueRelativeUriPaths } from "../../util/uri.js" -import { AutocompleteSnippet, AutocompleteSnippetType } from "../types.js" +import { AutocompleteSnippet } from "../types.js" type TemplateRenderer = ( prefix: string, @@ -49,13 +49,7 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { ) const otherFiles = snippets - .map((snippet, i) => { - if (snippet.type === AutocompleteSnippetType.Diff) { - return snippet.content - } - - return `+++++ ${getFileName(relativePaths[i])} \n${snippet.content}` - }) + .map((snippet, i) => `+++++ ${getFileName(relativePaths[i])} \n${snippet.content}`) .join("\n\n") return [`${otherFiles}\n\n+++++ ${getFileName(relativePaths[relativePaths.length - 1])}\n${prefix}`, suffix] @@ -93,13 +87,7 @@ const mercuryMultifileFimTemplate: AutocompleteTemplate = { ) const otherFiles = snippets - .map((snippet, i) => { - if (snippet.type === AutocompleteSnippetType.Diff) { - return snippet.content - } - - return `<|file_sep|>${getFileName(relativePaths[i])} \n${snippet.content}` - }) + .map((snippet, i) => `<|file_sep|>${getFileName(relativePaths[i])} \n${snippet.content}`) .join("\n\n") return [ diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/__tests__/formatOpenedFilesContext.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/__tests__/formatOpenedFilesContext.test.ts index b54a1e2871..33d3c9c917 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/__tests__/formatOpenedFilesContext.test.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/__tests__/formatOpenedFilesContext.test.ts @@ -4,7 +4,7 @@ */ import { describe, expect, test } from "vitest" -import { AutocompleteCodeSnippet, AutocompleteDiffSnippet, AutocompleteSnippetType } from "../../types" +import { AutocompleteCodeSnippet, AutocompleteSnippetType } from "../../types" import { HelperVars } from "../../util/HelperVars" import { formatOpenedFilesContext } from "../formatOpenedFilesContext" @@ -25,11 +25,6 @@ describe("formatOpenedFilesContext main function tests", () => { content, }) - const createDiffSnippet = (content: string): AutocompleteDiffSnippet => ({ - type: AutocompleteSnippetType.Diff, - content, - }) - test("should return empty array when no snippets are provided", () => { const result = formatOpenedFilesContext([], 1000, mockHelper, [], TOKEN_BUFFER) expect(result).toEqual([]) @@ -99,7 +94,7 @@ describe("formatOpenedFilesContext main function tests", () => { createCodeSnippet("file2.ts", "content of file 2"), ] - const alreadyAddedSnippets = [createDiffSnippet("diff content")] + const alreadyAddedSnippets = [createCodeSnippet("added.ts", "added content")] const result = formatOpenedFilesContext(snippets, 1000, mockHelper, alreadyAddedSnippets, TOKEN_BUFFER) diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/filtering.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/filtering.ts index c4d0941a7a..772f2f2ccc 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/filtering.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/filtering.ts @@ -44,7 +44,6 @@ export const getSnippets = (helper: HelperVars, payload: SnippetPayload): Autoco clipboard: payload.clipboardSnippets, recentlyVisitedRanges: payload.recentlyVisitedRangesSnippets, recentlyEditedRanges: payload.recentlyEditedRangeSnippets, - diff: payload.diffSnippets, recentlyOpenedFiles: payload.recentlyOpenedFileSnippets, base: shuffleArray( filterSnippetsAlreadyInCaretWindow( @@ -88,13 +87,6 @@ export const getSnippets = (helper: HelperVars, payload: SnippetPayload): Autoco defaultPriority: 4, snippets: payload.recentlyEditedRangeSnippets, }, - { - key: "diff", - enabledOrPriority: helper.options.experimental_includeDiff, - defaultPriority: 5, - snippets: payload.diffSnippets, - // TODO: diff is commonly too large, thus anything lower in priority is not included. - }, { key: "base", enabledOrPriority: true, diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/types.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/types.ts index 276440cb33..92fd0dfa20 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/types.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/types.ts @@ -1,9 +1,7 @@ -import { IDE, RangeInFileWithContents } from "../index" -import { AutocompleteLanguageInfo } from "./constants/AutocompleteLanguageInfo" +import { RangeInFileWithContents } from "../index" export enum AutocompleteSnippetType { Code = "code", - Diff = "diff", Clipboard = "clipboard", Static = "static", } @@ -18,10 +16,6 @@ export interface AutocompleteCodeSnippet extends BaseAutocompleteSnippet { type: AutocompleteSnippetType.Code } -export interface AutocompleteDiffSnippet extends BaseAutocompleteSnippet { - type: AutocompleteSnippetType.Diff -} - export interface AutocompleteClipboardSnippet extends BaseAutocompleteSnippet { type: AutocompleteSnippetType.Clipboard copiedAt: string @@ -32,20 +26,8 @@ export interface AutocompleteStaticSnippet extends BaseAutocompleteSnippet { filepath: string } -export type AutocompleteSnippet = - | AutocompleteCodeSnippet - | AutocompleteDiffSnippet - | AutocompleteClipboardSnippet - | AutocompleteStaticSnippet +export type AutocompleteSnippet = AutocompleteCodeSnippet | AutocompleteClipboardSnippet | AutocompleteStaticSnippet export type RankedSnippet = RangeInFileWithContents & { score?: number } - -export type GetLspDefinitionsFunction = ( - filepath: string, - contents: string, - cursorIndex: number, - ide: IDE, - lang: AutocompleteLanguageInfo, -) => Promise diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/diff/util.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/diff/util.test.ts deleted file mode 100644 index 58c569c8b1..0000000000 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/diff/util.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Generated by continue - -import { describe, expect, it, vi } from "vitest" -import { ChatMessage } from "../index" -import { generateLines, streamLines } from "./util" - -describe("streamLines", () => { - it("should split chunks into lines correctly", async () => { - async function* streamCompletion(): AsyncGenerator { - yield "line1\nline" - yield "2\nline3\n" - yield "line4" - } - - const resultLines: string[] = [] - for await (const line of streamLines(streamCompletion())) { - resultLines.push(line) - } - - expect(resultLines).toEqual(["line1", "line2", "line3", "line4"]) - }) - - it("should handle ChatMessage chunks", async () => { - const messageChunk1: ChatMessage = { - role: "assistant", - content: "line1\nline", - } - const messageChunk2: ChatMessage = { - role: "assistant", - content: "2\nline3\n", - } - const messageChunk3: ChatMessage = { - role: "assistant", - content: "line4", - } - - // const spy = vi.spyOn(messageContentModule, "renderChatMessage"); - - async function* streamCompletion(): AsyncGenerator { - yield messageChunk1 - yield messageChunk2 - yield messageChunk3 - } - - const resultLines: string[] = [] - for await (const line of streamLines(streamCompletion())) { - resultLines.push(line) - } - - expect(resultLines).toEqual(["line1", "line2", "line3", "line4"]) - // expect(spy).toHaveBeenCalledTimes(3); - }) - - it("should log lines if log parameter is true", async () => { - const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}) - - async function* streamCompletion(): AsyncGenerator { - yield "line1\nline2\n" - yield "line3" - } - - const resultLines: string[] = [] - for await (const line of streamLines(streamCompletion(), true)) { - resultLines.push(line) - } - - expect(resultLines).toEqual(["line1", "line2", "line3"]) - expect(consoleSpy).toHaveBeenCalledWith("Streamed lines: ", "line1\nline2\nline3") - - consoleSpy.mockRestore() - }) -}) - -describe("generateLines", () => { - it("should yield the lines provided in the array", async () => { - const lines = ["line1", "line2", "line3"] - const resultLines: string[] = [] - - for await (const line of generateLines(lines)) { - resultLines.push(line) - } - - expect(resultLines).toEqual(lines) - }) -}) diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/diff/util.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/diff/util.ts deleted file mode 100644 index d5da1720c3..0000000000 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/diff/util.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { ChatMessage } from "../index.js" -import { renderChatMessage } from "../util/messageContent.js" - -export type LineStream = AsyncGenerator - -/** - * Convert a stream of arbitrary chunks to a stream of lines - */ -export async function* streamLines( - streamCompletion: AsyncGenerator, - log: boolean = false, -): LineStream { - const allLines = [] - let buffer = "" - - try { - for await (const update of streamCompletion) { - const chunk = typeof update === "string" ? update : renderChatMessage(update) - buffer += chunk - const lines = buffer.split("\n") - buffer = lines.pop() ?? "" - for (const line of lines) { - yield line - allLines.push(line) - } - } - if (buffer.length > 0) { - yield buffer - allLines.push(buffer) - } - } finally { - if (log) { - console.log("Streamed lines: ", allLines.join("\n")) - } - } -} - -export async function* generateLines(lines: T[]): AsyncGenerator { - for (const line of lines) { - yield line - } -} diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts index 5585fe8a08..66b868d0c6 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts @@ -531,7 +531,6 @@ export interface TabAutocompleteOptions { experimental_includeClipboard: boolean | number experimental_includeRecentlyVisitedRanges: boolean | number experimental_includeRecentlyEditedRanges: boolean | number - experimental_includeDiff: boolean | number experimental_enableStaticContextualization: boolean } diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/messageContent.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/messageContent.ts deleted file mode 100644 index 1b5ed9fa83..0000000000 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/messageContent.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ChatMessage, MessageContent, TextMessagePart } from "../index" - -function stripImages(messageContent: MessageContent): string { - if (typeof messageContent === "string") { - return messageContent - } - - return messageContent - .filter((part) => part.type === "text") - .map((part) => (part as TextMessagePart).text) - .join("\n") -} - -export function renderChatMessage(message: ChatMessage): string { - switch (message?.role) { - case "user": - case "assistant": - case "system": - return stripImages(message.content) - default: - return "" - } -} diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/parameters.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/parameters.ts index 18ee68de27..403127b080 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/parameters.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/parameters.ts @@ -25,6 +25,5 @@ export const DEFAULT_AUTOCOMPLETE_OPTS: TabAutocompleteOptions = { experimental_includeClipboard: false, experimental_includeRecentlyVisitedRanges: true, experimental_includeRecentlyEditedRanges: true, - experimental_includeDiff: true, experimental_enableStaticContextualization: false, } diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/ranges.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/ranges.ts index 08598703da..abafd75ae9 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/ranges.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/ranges.ts @@ -13,34 +13,3 @@ export function getRangeInString(content: string, range: Range): string { return [firstLine, ...middleLines, lastLine].join("\n") } - -export function intersection(a: Range, b: Range): Range | null { - const startLine = Math.max(a.start.line, b.start.line) - const endLine = Math.min(a.end.line, b.end.line) - - if (startLine > endLine) { - return null - } - - if (startLine === endLine) { - const startCharacter = Math.max(a.start.character, b.start.character) - const endCharacter = Math.min(a.end.character, b.end.character) - - if (startCharacter > endCharacter) { - return null - } - - return { - start: { line: startLine, character: startCharacter }, - end: { line: endLine, character: endCharacter }, - } - } - - const startCharacter = startLine === a.start.line ? a.start.character : b.start.character - const endCharacter = endLine === a.end.line ? a.end.character : b.end.character - - return { - start: { line: startLine, character: startCharacter }, - end: { line: endLine, character: endCharacter }, - } -} diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.test.ts deleted file mode 100644 index 76e58188f4..0000000000 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, expect, it } from "vitest" -import { getSymbolsForFile } from "./treeSitter" - -// vibecoded -describe("getSymbolsForFile", () => { - it("should extract symbols from Python code", async () => { - const filepath = "test.py" - const contents = `def greet(name): - return f"Hello, {name}!" - -class Calculator: - def add(self, a, b): - return a + b -` - - const symbols = await getSymbolsForFile(filepath, contents) - - // Verify we get symbols - expect(symbols).toBeDefined() - expect(symbols!.length).toBeGreaterThan(0) - - // Verify function symbol - const greetSymbol = symbols?.find((s) => s.name === "greet") - expect(greetSymbol).toBeDefined() - expect(greetSymbol?.type).toBe("function_definition") - expect(greetSymbol?.filepath).toBe(filepath) - expect(greetSymbol?.range.start.line).toBe(0) - expect(greetSymbol?.content).toContain("def greet") - - // Verify class symbol - const calculatorSymbol = symbols?.find((s) => s.name === "Calculator") - expect(calculatorSymbol).toBeDefined() - expect(calculatorSymbol?.type).toBe("class_definition") - expect(calculatorSymbol?.content).toContain("class Calculator") - }) -}) diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.ts index a8aa4961f6..a3afdd03a2 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.ts @@ -6,7 +6,6 @@ type Language = Parser.Language type SyntaxNode = Parser.SyntaxNode type Query = Parser.Query type Tree = Parser.Tree -import { SymbolWithRange } from ".." import { getUriFileExtension } from "./uri" export enum LanguageName { @@ -259,87 +258,3 @@ async function loadLanguageForFileExt(fileExtension: string): Promise return await Language.load(wasmPath) } - -// See https://tree-sitter.github.io/tree-sitter/using-parsers -const GET_SYMBOLS_FOR_NODE_TYPES: SyntaxNode["type"][] = [ - "class_declaration", - "class_definition", - "function_item", // function name = first "identifier" child - "function_definition", - "method_declaration", // method name = first "identifier" child - "method_definition", - "generator_function_declaration", - // property_identifier - // field_declaration - // "arrow_function", -] - -export async function getSymbolsForFile(filepath: string, contents: string): Promise { - //MINIMAL_REPO - continue doesn't use this in autocomplete - const parser = await getParserForFile(filepath) - if (!parser) { - return - } - - let tree: Tree | null - try { - tree = parser.parse(contents) - } catch { - console.log(`Error parsing file: ${filepath}`) - return - } - - if (!tree) { - console.log(`Failed to parse file: ${filepath}`) - return - } - // console.log(`file: ${filepath}`); - - // Function to recursively find all named nodes (classes and functions) - const symbols: SymbolWithRange[] = [] - function findNamedNodesRecursive(node: SyntaxNode) { - // console.log(`node: ${node.type}, ${node.text}`); - if (GET_SYMBOLS_FOR_NODE_TYPES.includes(node.type)) { - // console.log(`parent: ${node.type}, ${node.text.substring(0, 200)}`); - // node.children.forEach((child) => { - // console.log(`child: ${child.type}, ${child.text}`); - // }); - - // Empirically, the actual name is the last identifier in the node - // Especially with languages where return type is declared before the name - // TODO use findLast in newer version of node target - let identifier: SyntaxNode | undefined = undefined - for (let i = node.children.length - 1; i >= 0; i--) { - const child = node.children[i] - if (child && (child.type === "identifier" || child.type === "property_identifier")) { - identifier = child - break - } - } - - if (identifier?.text) { - symbols.push({ - filepath, - type: node.type, - name: identifier.text, - range: { - start: { - character: node.startPosition.column, - line: node.startPosition.row, - }, - end: { - character: node.endPosition.column + 1, - line: node.endPosition.row + 1, - }, - }, - content: node.text, - }) - } - } - node.children.forEach((child) => { - if (child) findNamedNodesRecursive(child) - }) - } - findNamedNodesRecursive(tree.rootNode) - return symbols -} diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/autocomplete/lsp.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/autocomplete/lsp.ts index a037487f2d..c13ba3a028 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/autocomplete/lsp.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/autocomplete/lsp.ts @@ -1,22 +1,6 @@ -import { AutocompleteLanguageInfo } from "../../../autocomplete/constants/AutocompleteLanguageInfo" -import { AutocompleteCodeSnippet, AutocompleteSnippetType } from "../../../autocomplete/types" -import { GetLspDefinitionsFunction } from "../../../autocomplete/types" -import { getAst, getTreePathAtCursor } from "../../../autocomplete/util/ast" -import { intersection } from "../../../util/ranges" - import * as vscode from "vscode" -import type { DocumentSymbol, IDE, Range, RangeInFile, RangeInFileWithContents, SignatureHelp } from "../../../" -import type Parser from "web-tree-sitter" -type SyntaxNode = Parser.SyntaxNode -const FUNCTION_BLOCK_NODE_TYPES = ["block", "statement_block"] -const FUNCTION_DECLARATION_NODE_TYPEs = [ - "method_definition", - "function_definition", - "function_item", - "function_declaration", - "method_declaration", -] +import type { DocumentSymbol, RangeInFile, SignatureHelp } from "../../../" type GotoProviderName = | "vscode.executeDefinitionProvider" @@ -120,283 +104,6 @@ export async function executeGotoProvider(input: GotoInput): Promise boolean, firstN?: number): SyntaxNode[] { - let matchingNodes: SyntaxNode[] = [] - - if (firstN && firstN <= 0) { - return [] - } - - // Check if the current node's type is in the list of types we're interested in - if (predicate(node)) { - matchingNodes.push(node) - } - - // Recursively search for matching types in all children of the current node - for (const child of node.children) { - if (!child) continue - matchingNodes = matchingNodes.concat( - findChildren(child, predicate, firstN ? firstN - matchingNodes.length : undefined), - ) - } - - return matchingNodes -} - -function findTypeIdentifiers(node: SyntaxNode): SyntaxNode[] { - return findChildren( - node, - (childNode) => - childNode.type === "type_identifier" || - (["ERROR"].includes(childNode.parent?.type ?? "") && - childNode.type === "identifier" && - childNode.text[0].toUpperCase() === childNode.text[0]), - ) -} - -async function crawlTypes( - rif: RangeInFile | RangeInFileWithContents, - ide: IDE, - depth: number = 1, - results: RangeInFileWithContents[] = [], - searchedLabels: Set = new Set(), -): Promise { - // Get the file contents if not already attached - const contents = isRifWithContents(rif) ? rif.contents : await ide.readFile(rif.filepath) - - // Parse AST - const ast = await getAst(rif.filepath, contents) - if (!ast) { - return results - } - const astLineCount = ast.rootNode.text.split("\n").length - - // Find type identifiers - const identifierNodes = findTypeIdentifiers(ast.rootNode).filter((node) => !searchedLabels.has(node.text)) - // Don't search for the same type definition more than once - // We deduplicate below to be sure, but this saves calls to the LSP - identifierNodes.forEach((node) => searchedLabels.add(node.text)) - - // Use LSP to get the definitions of those types - const definitions = [] - - for (const node of identifierNodes) { - const [typeDef] = await executeGotoProvider({ - uri: vscode.Uri.parse(rif.filepath), - // TODO: tree-sitter is zero-indexed, but there seems to be an off-by-one - // error at least with the .ts parser sometimes - line: rif.range.start.line + Math.min(node.startPosition.row, astLineCount - 1), - character: rif.range.start.character + node.startPosition.column, - name: "vscode.executeDefinitionProvider", - }) - - if (!typeDef) { - definitions.push(undefined) - continue - } - - const contents = await ide.readRangeInFile(typeDef.filepath, typeDef.range) - - definitions.push({ - ...typeDef, - contents, - }) - } - - // TODO: Filter out if not in our code? - - // Filter out duplicates - for (const definition of definitions) { - if ( - !definition || - results.some( - (result) => result.filepath === definition.filepath && intersection(result.range, definition.range) !== null, - ) - ) { - continue // ;) - } - results.push(definition) - } - - // Recurse - if (depth > 0) { - for (const result of [...results]) { - await crawlTypes(result, ide, depth - 1, results, searchedLabels) - } - } - - return results -} - -async function getDefinitionsForNode( - uri: vscode.Uri, - node: SyntaxNode, - ide: IDE, - lang: AutocompleteLanguageInfo, -): Promise { - const ranges: (RangeInFile | RangeInFileWithContents)[] = [] - switch (node.type) { - case "call_expression": { - // function call -> function definition - const [funDef] = await executeGotoProvider({ - uri, - line: node.startPosition.row, - character: node.startPosition.column, - name: "vscode.executeDefinitionProvider", - }) - if (!funDef) { - return [] - } - - // Don't display a function of more than 15 lines - // We can of course do something smarter here eventually - let funcText = await ide.readRangeInFile(funDef.filepath, funDef.range) - if (funcText.split("\n").length > 15) { - let truncated = false - const funRootAst = await getAst(funDef.filepath, funcText) - if (funRootAst) { - const [funNode] = findChildren( - funRootAst?.rootNode, - (node) => FUNCTION_DECLARATION_NODE_TYPEs.includes(node.type), - 1, - ) - if (funNode) { - const [statementBlockNode] = findChildren( - funNode, - (node) => FUNCTION_BLOCK_NODE_TYPES.includes(node.type), - 1, - ) - if (statementBlockNode) { - funcText = funRootAst.rootNode.text.slice(0, statementBlockNode.startIndex).trim() - truncated = true - } - } - } - if (!truncated) { - funcText = funcText.split("\n")[0] - } - } - - ranges.push(funDef) - - const typeDefs = await crawlTypes( - { - ...funDef, - contents: funcText, - }, - ide, - ) - ranges.push(...typeDefs) - break - } - case "variable_declarator": - // variable assignment -> variable definition/type - // usages of the var that appear after the declaration - break - case "impl_item": - // impl of trait -> trait definition - break - case "new_expression": { - // In 'new MyClass(...)', "MyClass" is the classNameNode - const classNameNode = node.children.find((child) => child && child.type === "identifier") - const [classDef] = await executeGotoProvider({ - uri, - line: (classNameNode ?? node).endPosition.row, - character: (classNameNode ?? node).endPosition.column, - name: "vscode.executeDefinitionProvider", - }) - if (!classDef) { - break - } - const contents = await ide.readRangeInFile(classDef.filepath, classDef.range) - - ranges.push({ - ...classDef, - contents: `${ - classNameNode?.text ? `${lang.singleLineComment} ${classNameNode.text}:\n` : "" - }${contents.trim()}`, - }) - - const definitions = await crawlTypes({ ...classDef, contents }, ide) - ranges.push(...definitions.filter(Boolean)) - - break - } - case "": - // function definition -> implementations? - break - } - return await Promise.all( - ranges.map(async (rif) => { - // Convert the VS Code Range type to ours - const range: Range = { - start: { - line: rif.range.start.line, - character: rif.range.start.character, - }, - end: { - line: rif.range.end.line, - character: rif.range.end.character, - }, - } - rif.range = range - - if (!isRifWithContents(rif)) { - return { - ...rif, - contents: await ide.readRangeInFile(rif.filepath, rif.range), - } - } - return rif - }), - ) -} - -/** - * and other stuff not directly on the path: - * - variables defined on line above - * ...etc... - */ - -export const getDefinitionsFromLsp: GetLspDefinitionsFunction = async ( - filepath: string, - contents: string, - cursorIndex: number, - ide: IDE, - lang: AutocompleteLanguageInfo, -): Promise => { - try { - const ast = await getAst(filepath, contents) - if (!ast) { - return [] - } - - const treePath = await getTreePathAtCursor(ast, cursorIndex) - if (!treePath) { - return [] - } - - const results: RangeInFileWithContents[] = [] - for (const node of treePath.reverse()) { - const definitions = await getDefinitionsForNode(vscode.Uri.parse(filepath), node, ide, lang) - results.push(...definitions) - } - - return results.map((result) => ({ - filepath: result.filepath, - content: result.contents, - type: AutocompleteSnippetType.Code, - })) - } catch (e) { - console.warn("Error getting definitions from LSP: ", e) - return [] - } -} - type SymbolProviderName = "vscode.executeDocumentSymbolProvider" interface SymbolInput { diff --git a/packages/kilo-vscode/src/services/autocomplete/next-edit/NextEditSuggestionManager.ts b/packages/kilo-vscode/src/services/autocomplete/next-edit/NextEditSuggestionManager.ts index a8ba61f16b..904b9e20dd 100644 --- a/packages/kilo-vscode/src/services/autocomplete/next-edit/NextEditSuggestionManager.ts +++ b/packages/kilo-vscode/src/services/autocomplete/next-edit/NextEditSuggestionManager.ts @@ -122,10 +122,6 @@ export class NextEditSuggestionManager implements vscode.Disposable { return this.pending !== null } - public getPending(): PendingNextEdit | null { - return this.pending - } - public setPending(p: PendingNextEdit): void { this.clearDecorations() this.pending = p diff --git a/packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts b/packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts index 9b0e3d9957..27ec377a33 100644 --- a/packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts +++ b/packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts @@ -23,7 +23,6 @@ function toPosix(filePath: string): string { export class FileIgnoreController { private workspacePath: string private ignoreInstance: Ignore = ignore() - private loadedContents: Array<{ file: string; content: string }> = [] private readonly realpathCache = new Map() constructor(workspacePath?: string) { @@ -32,7 +31,6 @@ export class FileIgnoreController { async initialize(): Promise { this.ignoreInstance = ignore() - this.loadedContents = [] this.realpathCache.clear() if (!this.workspacePath) { @@ -48,7 +46,6 @@ export class FileIgnoreController { if (kilocodeignoreContent.trim()) { this.ignoreInstance.add(kilocodeignoreContent) this.ignoreInstance.add(KILOCODEIGNORE) - this.loadedContents.push({ file: KILOCODEIGNORE, content: kilocodeignoreContent }) return } } @@ -59,7 +56,6 @@ export class FileIgnoreController { const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8") if (gitignoreContent.trim()) { this.ignoreInstance.add(gitignoreContent) - this.loadedContents.push({ file: GITIGNORE, content: gitignoreContent }) } } @@ -127,31 +123,7 @@ export class FileIgnoreController { return !this.ignoreInstance.ignores(relative) } - /** - * Filter a list of candidate paths to those allowed. - * When no workspace path was provided, returns an empty array. - */ - filterPaths(paths: string[]): string[] { - if (!this.workspacePath) { - return [] - } - return paths.filter((candidate) => this.validateAccess(candidate)) - } - - /** - * Returns user-facing instructions explaining why access is restricted. - */ - getInstructions(): string | undefined { - if (this.loadedContents.length === 0) { - return undefined - } - - const sections = this.loadedContents.map(({ file, content }) => `# ${file}\n\n${content.trimEnd()}`) - return sections.join("\n\n") - } - dispose(): void { - this.loadedContents = [] this.realpathCache.clear() this.ignoreInstance = ignore() } diff --git a/packages/kilo-vscode/src/services/browser-automation/browser-automation-service.ts b/packages/kilo-vscode/src/services/browser-automation/browser-automation-service.ts index ed7ca9bf3c..5afdf98a8d 100644 --- a/packages/kilo-vscode/src/services/browser-automation/browser-automation-service.ts +++ b/packages/kilo-vscode/src/services/browser-automation/browser-automation-service.ts @@ -1,13 +1,12 @@ import * as vscode from "vscode" -import type { KiloClient, McpStatus } from "@kilocode/sdk/v2/client" +import type { KiloClient } from "@kilocode/sdk/v2/client" import type { KiloConnectionService } from "../cli-backend" -export type BrowserAutomationState = "disabled" | "registering" | "connected" | "failed" | "disconnected" +type BrowserAutomationState = "disabled" | "registering" | "connected" | "failed" | "disconnected" export class BrowserAutomationService implements vscode.Disposable { private state: BrowserAutomationState = "disabled" private disposables: vscode.Disposable[] = [] - private stateListeners: Array<(state: BrowserAutomationState) => void> = [] // MCP server name used when registering with the CLI backend private static readonly MCP_SERVER_NAME = "kilo-playwright" @@ -23,22 +22,6 @@ export class BrowserAutomationService implements vscode.Disposable { ) } - /** Current state */ - getState(): BrowserAutomationState { - return this.state - } - - /** Subscribe to state changes */ - onStateChange(listener: (state: BrowserAutomationState) => void): () => void { - this.stateListeners.push(listener) - return () => { - const idx = this.stateListeners.indexOf(listener) - if (idx >= 0) { - this.stateListeners.splice(idx, 1) - } - } - } - /** * Read settings and enable/disable accordingly. * Called on construction and when settings change. @@ -150,24 +133,6 @@ export class BrowserAutomationService implements vscode.Disposable { this.setState("disabled") } - /** - * Get the current MCP server status from the CLI backend. - */ - async getServerStatus(): Promise { - const client = this.getClient() - if (!client) { - return null - } - - try { - const directory = this.getWorkspaceDirectory() - const { data: allStatus } = await client.mcp.status({ directory }, { throwOnError: true }) - return allStatus[BrowserAutomationService.MCP_SERVER_NAME] ?? null - } catch { - return null - } - } - private getClient(): KiloClient | null { try { return this.connectionService.getClient() @@ -190,9 +155,6 @@ export class BrowserAutomationService implements vscode.Disposable { } console.log(`[Kilo New] BrowserAutomationService: State ${this.state} → ${state}`) this.state = state - for (const listener of this.stateListeners) { - listener(state) - } } dispose(): void { @@ -200,6 +162,5 @@ export class BrowserAutomationService implements vscode.Disposable { d.dispose() } this.disposables = [] - this.stateListeners = [] } } diff --git a/packages/kilo-vscode/src/services/cli-backend/connection-service.ts b/packages/kilo-vscode/src/services/cli-backend/connection-service.ts index 965d848541..76a8b46195 100644 --- a/packages/kilo-vscode/src/services/cli-backend/connection-service.ts +++ b/packages/kilo-vscode/src/services/cli-backend/connection-service.ts @@ -21,9 +21,11 @@ function isNotFound(err: unknown) { if (!err || typeof err !== "object") return false const obj = err as Record if (obj.name === "NotFoundError") return true + if (obj._tag === "NotFound") return true if (obj.status === 404) return true if (obj.data && typeof obj.data === "object") { - return (obj.data as Record).name === "NotFoundError" + const data = obj.data as Record + return data.name === "NotFoundError" || data._tag === "NotFound" } return false } @@ -69,6 +71,8 @@ export class KiloConnectionService { private readonly clearPendingPromptsListeners: Set = new Set() private readonly directoryProviders: Set = new Set() private readonly permissionDirectories: Map = new Map() + private readonly questionDirectories: Map = new Map() + private questionRevision = 0 /** * Shared mapping used to resolve session scope for events that don't reliably include a sessionID. @@ -270,6 +274,36 @@ export class KiloConnectionService { } } + recordQuestionDirectory(requestID: string, directory: string): void { + if (!requestID || !directory) { + return + } + this.questionDirectories.set(requestID, directory) + } + + getQuestionDirectory(requestID: string): string | undefined { + return this.questionDirectories.get(requestID) + } + + clearQuestionDirectory(requestID: string): void { + this.questionDirectories.delete(requestID) + // A resolved request must invalidate an in-flight recovery scan so stale list data cannot repost it. + this.questionRevision += 1 + } + + getQuestionRevision(): number { + return this.questionRevision + } + + pruneQuestionDirectories(active: Set, dirs: Set): void { + const size = this.questionDirectories.size + for (const [id, dir] of this.questionDirectories) { + if (active.has(id) || !dirs.has(dir)) continue + this.questionDirectories.delete(id) + } + if (this.questionDirectories.size !== size) this.questionRevision += 1 + } + /** * Subscribe to notification dismiss events broadcast from any KiloProvider. Returns unsubscribe function. */ @@ -429,7 +463,7 @@ export class KiloConnectionService { if (qs) { for (const q of qs) { const { error } = await this.client.question.reject({ requestID: q.id, directory: dir }) - if (error) throw new Error(`Failed to reject question ${q.id}: ${String(error)}`) + if (error && !isNotFound(error)) throw new Error(`Failed to reject question ${q.id}: ${String(error)}`) } } await drainSuggestions(this.client, dir) @@ -516,6 +550,8 @@ export class KiloConnectionService { this.directoryProviders.clear() this.messageSessionIdsByMessageId.clear() this.permissionDirectories.clear() + this.questionDirectories.clear() + this.questionRevision += 1 this.focused.clear() this.opened.clear() if (this.debounceTimer) { @@ -595,6 +631,8 @@ export class KiloConnectionService { this.config = null this.info = null this.permissionDirectories.clear() + this.questionDirectories.clear() + this.questionRevision += 1 } private handleServerExit(code: number | null): void { @@ -647,6 +685,7 @@ export class KiloConnectionService { sse.onEvent((event, directory) => { if (this.sseClient !== sse) return this.handlePermissionEvent(event, directory) + this.handleQuestionEvent(event, directory) for (const listener of this.eventListeners) { listener(event, directory) } @@ -702,6 +741,17 @@ export class KiloConnectionService { this.clearPermissionDirectory(event.properties.requestID) } } + + private handleQuestionEvent(event: SSEPayload, directory?: string): void { + if (event.type === "question.asked" && directory) { + this.questionRevision += 1 + this.recordQuestionDirectory(event.properties.id, directory) + return + } + if (event.type === "question.replied" || event.type === "question.rejected") { + this.clearQuestionDirectory(event.properties.requestID) + } + } } async function drainSuggestions(client: KiloClient, directory: string): Promise { diff --git a/packages/kilo-vscode/src/services/marketplace/api.ts b/packages/kilo-vscode/src/services/marketplace/api.ts index 5c1bd7c3c4..8dbd3c5a9b 100644 --- a/packages/kilo-vscode/src/services/marketplace/api.ts +++ b/packages/kilo-vscode/src/services/marketplace/api.ts @@ -136,10 +136,6 @@ export class MarketplaceApiClient { } } - clearCache(): void { - this.cache.clear() - } - dispose(): void { this.cache.clear() } diff --git a/packages/kilo-vscode/src/shared/custom-provider.ts b/packages/kilo-vscode/src/shared/custom-provider.ts index b8ed2dfa29..12150b198c 100644 --- a/packages/kilo-vscode/src/shared/custom-provider.ts +++ b/packages/kilo-vscode/src/shared/custom-provider.ts @@ -149,13 +149,23 @@ export function sanitizeCustomProviderConfig(provider: unknown): { value: Saniti } type AnyRecord = Record +type ProviderPatch = Omit & { + models: Record< + string, + null | { + name: string + reasoning?: true | null + variants?: Record + } + > +} function isRecord(v: unknown): v is AnyRecord { return !!v && typeof v === "object" && !Array.isArray(v) } /** - * Build a provider patch that includes null sentinels for models and variants + * Build a provider patch that includes null sentinels for model properties * that existed in the previous config but are absent from the new one. The CLI * `config.update` endpoint deep-merges the payload with the existing config; * without explicit nulls, removed entries would persist on disk. @@ -163,7 +173,7 @@ function isRecord(v: unknown): v is AnyRecord { export function withCustomProviderDeletions(existing: unknown, next: SanitizedProviderConfig): SanitizedProviderConfig { if (!isRecord(existing)) return next const oldModels = isRecord(existing.models) ? existing.models : {} - const patched: AnyRecord = { ...next.models } + const patched: ProviderPatch["models"] = { ...next.models } for (const id of Object.keys(oldModels)) { if (!(id in patched)) { @@ -171,15 +181,21 @@ export function withCustomProviderDeletions(existing: unknown, next: SanitizedPr continue } const oldModel = oldModels[id] - const oldVariants = isRecord(oldModel) && isRecord(oldModel.variants) ? oldModel.variants : {} const newModel = patched[id] - if (!isRecord(newModel)) continue + if (!isRecord(oldModel) || !isRecord(newModel)) continue + const oldVariants = isRecord(oldModel.variants) ? oldModel.variants : {} const newVariants = isRecord(newModel.variants) ? newModel.variants : {} - const removedVariants = Object.keys(oldVariants).filter((v) => !(v in newVariants)) - if (removedVariants.length === 0) continue - const nulls = Object.fromEntries(removedVariants.map((v) => [v, null])) - patched[id] = { ...newModel, variants: { ...newVariants, ...nulls } } + const removed = Object.keys(oldVariants).filter((variant) => !(variant in newVariants)) + const variants = + removed.length > 0 + ? { ...newVariants, ...Object.fromEntries(removed.map((variant) => [variant, null])) } + : newModel.variants + patched[id] = { + ...newModel, + ...(variants ? { variants } : {}), + ...(oldModel.reasoning !== undefined && newModel.reasoning === undefined ? { reasoning: null } : {}), + } } - return { ...next, models: patched as SanitizedProviderConfig["models"] } + return { ...next, models: patched } as SanitizedProviderConfig } diff --git a/packages/kilo-vscode/tests/unit/autocomplete-error-backoff.test.ts b/packages/kilo-vscode/tests/unit/autocomplete-error-backoff.test.ts index 46c986b972..fab5cbbcc6 100644 --- a/packages/kilo-vscode/tests/unit/autocomplete-error-backoff.test.ts +++ b/packages/kilo-vscode/tests/unit/autocomplete-error-backoff.test.ts @@ -81,7 +81,6 @@ describe("ErrorBackoff", () => { }) it("is not fatal initially", () => { - expect(backoff.isFatal()).toBe(false) expect(backoff.getFatalStatus()).toBeNull() }) @@ -89,21 +88,18 @@ describe("ErrorBackoff", () => { it("blocks after a 402 error", () => { backoff.failure(new Error("SSE failed: 402 Payment Required")) expect(backoff.blocked()).toBe(true) - expect(backoff.isFatal()).toBe(true) expect(backoff.getFatalStatus()).toBe(402) }) it("blocks after a 401 error", () => { backoff.failure(new Error("SSE failed: 401 Unauthorized")) expect(backoff.blocked()).toBe(true) - expect(backoff.isFatal()).toBe(true) expect(backoff.getFatalStatus()).toBe(401) }) it("blocks after a 403 error", () => { backoff.failure(new Error("SSE failed: 403 Forbidden")) expect(backoff.blocked()).toBe(true) - expect(backoff.isFatal()).toBe(true) expect(backoff.getFatalStatus()).toBe(403) }) @@ -168,7 +164,6 @@ describe("ErrorBackoff", () => { backoff.reset() expect(backoff.blocked()).toBe(false) - expect(backoff.isFatal()).toBe(false) expect(backoff.getFatalStatus()).toBeNull() }) @@ -178,7 +173,6 @@ describe("ErrorBackoff", () => { backoff.success() expect(backoff.blocked()).toBe(false) - expect(backoff.isFatal()).toBe(false) }) }) @@ -232,7 +226,6 @@ describe("ErrorBackoff", () => { it("fatal error overrides retriable backoff", () => { backoff.failure(new Error("SSE failed: 500 Internal Server Error")) backoff.failure(new Error("SSE failed: 402 Payment Required")) - expect(backoff.isFatal()).toBe(true) expect(backoff.blocked()).toBe(true) }) @@ -244,7 +237,6 @@ describe("ErrorBackoff", () => { backoff.success() expect(backoff.blocked()).toBe(false) - expect(backoff.isFatal()).toBe(false) }) }) }) diff --git a/packages/kilo-vscode/tests/unit/connection-service-question.test.ts b/packages/kilo-vscode/tests/unit/connection-service-question.test.ts new file mode 100644 index 0000000000..a7c1df6466 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/connection-service-question.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, test } from "bun:test" +import { KiloConnectionService } from "../../src/services/cli-backend/connection-service" + +describe("KiloConnectionService question routing", () => { + test("ignores stale NotFoundError rejects while draining questions", async () => { + const service = new KiloConnectionService({} as any) + const client = { + permission: { + list: async () => ({ data: [] }), + }, + question: { + list: async () => ({ data: [{ id: "que_test" }] }), + reject: async () => ({ error: { _tag: "NotFound" } }), + }, + suggestion: { + list: async () => ({ data: [] }), + }, + network: { + list: async () => ({ data: [] }), + }, + } + + ;(service as any).client = client + ;(service as any).directoryProviders.add(() => ["/tmp/workspace"]) + + await expect(service.drainPendingPrompts()).resolves.toBeUndefined() + }) + + test("records and clears request origins from SSE events", () => { + const service = new KiloConnectionService({} as any) + const handler = service as unknown as { + handleQuestionEvent(event: unknown, directory?: string): void + } + + handler.handleQuestionEvent( + { type: "question.asked", properties: { id: "que_test", sessionID: "ses_test", questions: [] } }, + "/tmp/worktree", + ) + expect(service.getQuestionDirectory("que_test")).toBe("/tmp/worktree") + expect(service.getQuestionRevision()).toBe(1) + + handler.handleQuestionEvent({ + type: "question.replied", + properties: { requestID: "que_test", sessionID: "ses_test", answers: [] }, + }) + expect(service.getQuestionDirectory("que_test")).toBeUndefined() + expect(service.getQuestionRevision()).toBe(2) + + service.recordQuestionDirectory("que_rejected", "/tmp/worktree") + handler.handleQuestionEvent({ + type: "question.rejected", + properties: { requestID: "que_rejected", sessionID: "ses_test" }, + }) + expect(service.getQuestionDirectory("que_rejected")).toBeUndefined() + expect(service.getQuestionRevision()).toBe(3) + }) + + test("prunes stale origins only for successfully scanned directories", () => { + const service = new KiloConnectionService({} as any) + service.recordQuestionDirectory("que_active", "/tmp/scanned") + service.recordQuestionDirectory("que_stale", "/tmp/scanned") + service.recordQuestionDirectory("que_unknown", "/tmp/failed") + + service.pruneQuestionDirectories(new Set(["que_active"]), new Set(["/tmp/scanned"])) + + expect(service.getQuestionDirectory("que_active")).toBe("/tmp/scanned") + expect(service.getQuestionDirectory("que_stale")).toBeUndefined() + expect(service.getQuestionDirectory("que_unknown")).toBe("/tmp/failed") + expect(service.getQuestionRevision()).toBe(1) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/custom-provider.test.ts b/packages/kilo-vscode/tests/unit/custom-provider.test.ts index 1a3ce2c242..4b7069bc52 100644 --- a/packages/kilo-vscode/tests/unit/custom-provider.test.ts +++ b/packages/kilo-vscode/tests/unit/custom-provider.test.ts @@ -166,11 +166,12 @@ describe("withCustomProviderDeletions", () => { expect(models.gone).toBeNull() }) - it("emits null for variants removed from a surviving model", () => { + it("emits null for reasoning and variants removed from a surviving model", () => { const existing = { models: { keep: { name: "Keep", + reasoning: true, variants: { high: { reasoningEffort: "high" }, low: { reasoningEffort: "low" } }, }, }, @@ -182,9 +183,11 @@ describe("withCustomProviderDeletions", () => { }, } as typeof baseNext const result = withCustomProviderDeletions(existing, next) - const model = (result.models as Record }>).keep - expect(model.variants.high).toEqual({ reasoningEffort: "high" }) - expect(model.variants.low).toBeNull() + const model = (result.models as Record }>) + .keep + expect(model.reasoning).toBeNull() + expect(model.variants?.high).toEqual({ reasoningEffort: "high" }) + expect(model.variants?.low).toBeNull() }) it("does not touch variants on a model that is being deleted", () => { diff --git a/packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts b/packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts index e590f92b36..ba4fd3c46e 100644 --- a/packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts +++ b/packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts @@ -40,7 +40,6 @@ describe("FileIgnoreController", () => { expect(controller.validateAccess("secret/keys.txt")).toBe(false) expect(controller.validateAccess(path.join(workspace, "a.snap"))).toBe(false) expect(controller.validateAccess(path.join(workspace, "src", "main.ts"))).toBe(true) - expect(controller.getInstructions()).toContain(".kilocodeignore") }) it("does NOT block .env files unless explicitly listed", async () => { @@ -81,7 +80,6 @@ describe("FileIgnoreController", () => { expect(controller.validateAccess(path.join(workspace, "node_modules", "foo.js"))).toBe(false) expect(controller.validateAccess(path.join(workspace, "build", "output.js"))).toBe(false) expect(controller.validateAccess(path.join(workspace, "src", "main.ts"))).toBe(true) - expect(controller.getInstructions()).toContain(".gitignore") }) it("blocks .env files via hardcoded sensitive patterns", async () => { @@ -167,12 +165,5 @@ describe("FileIgnoreController", () => { expect(controller.validateAccess("/some/file.ts")).toBe(false) expect(controller.validateAccess("relative/file.ts")).toBe(false) }) - - it("filterPaths returns empty array", async () => { - const controller = new FileIgnoreController("") - await controller.initialize() - - expect(controller.filterPaths(["/some/file.ts", "other.ts"])).toEqual([]) - }) }) }) diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts index f2ee5c2f7e..d220307a77 100644 --- a/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts @@ -158,6 +158,121 @@ describe("sessionToWebview", () => { expect(() => new Date(result.createdAt)).not.toThrow() expect(new Date(result.createdAt).getTime()).toBe(1700000000000) }) + + it("clears optional state omitted from a full session snapshot", () => { + const result = sessionToWebview(makeSession()) + expect(result.revert).toBeNull() + expect(result.summary).toBeNull() + }) +}) + +describe("applySessionPatch", () => { + it("applies values without dropping unrelated session fields", () => { + const session = makeSession({ + workspaceID: "workspace-1", + cost: 1, + tokens: { input: 1, output: 2, reasoning: 3, cache: { read: 4, write: 5 } }, + }) + + const result = applySessionPatch(session, { title: "Updated", cost: 2, time: { updated: 1700002000000 } }) + + expect(result.title).toBe("Updated") + expect(result.cost).toBe(2) + expect(result.workspaceID).toBe("workspace-1") + expect(result.tokens).toEqual(session.tokens) + expect(result.time).toEqual({ created: 1700000000000, updated: 1700002000000 }) + }) + + it("clears optional fields when the patch contains null", () => { + const session = makeSession({ + workspaceID: "workspace-1", + summary: { additions: 1, deletions: 2, files: 3 }, + cost: 4, + tokens: { input: 1, output: 2, reasoning: 3, cache: { read: 4, write: 5 } }, + share: { url: "https://example.com" }, + agent: "code", + model: { id: "model-1", providerID: "provider-1" }, + permission: [], + revert: { messageID: "msg-1" }, + time: { created: 1, updated: 2, compacting: 3, archived: 4 }, + }) + + const result = applySessionPatch(session, { + workspaceID: null, + summary: null, + cost: null, + tokens: null, + share: { url: null }, + agent: null, + model: null, + permission: null, + revert: null, + time: { compacting: null, archived: null }, + }) + + expect(result.workspaceID).toBeUndefined() + expect(result.summary).toBeUndefined() + expect(result.cost).toBeUndefined() + expect(result.tokens).toBeUndefined() + expect(result.share).toBeUndefined() + expect(result.agent).toBeUndefined() + expect(result.model).toBeUndefined() + expect(result.permission).toBeUndefined() + expect(result.revert).toBeUndefined() + expect(result.time).toEqual({ created: 1, updated: 2 }) + }) + + it("ignores null patches for required session fields", () => { + const session = makeSession() + const result = applySessionPatch(session, { + slug: null, + projectID: null, + directory: null, + title: null, + version: null, + time: { created: null, updated: null }, + }) + + expect(result.slug).toBe(session.slug) + expect(result.projectID).toBe(session.projectID) + expect(result.directory).toBe(session.directory) + expect(result.title).toBe(session.title) + expect(result.version).toBe(session.version) + expect(result.time).toEqual(session.time) + }) + + it("does not mutate the original session", () => { + const session = makeSession({ revert: { messageID: "msg-1" }, time: { created: 1, updated: 2, compacting: 3 } }) + + applySessionPatch(session, { revert: null, time: { updated: 4, compacting: null } }) + + expect(session.revert).toEqual({ messageID: "msg-1" }) + expect(session.time).toEqual({ created: 1, updated: 2, compacting: 3 }) + }) +}) + +describe("sessionPatchToWebview", () => { + it("converts relevant patch timestamps and preserves explicit clears", () => { + const result = sessionPatchToWebview("sess-1", { + parentID: null, + summary: null, + revert: null, + time: { created: 1700000000000, updated: 1700001000000 }, + }) + + expect(result).toEqual({ + id: "sess-1", + parentID: null, + summary: null, + revert: null, + createdAt: new Date(1700000000000).toISOString(), + updatedAt: new Date(1700001000000).toISOString(), + }) + }) + + it("omits fields not present in the patch", () => { + expect(sessionPatchToWebview("sess-1", { cost: 2 })).toEqual({ id: "sess-1" }) + }) }) describe("applySessionPatch", () => { diff --git a/packages/kilo-vscode/tests/unit/permission-description.test.ts b/packages/kilo-vscode/tests/unit/permission-description.test.ts index 34d6742f1f..d67145ed20 100644 --- a/packages/kilo-vscode/tests/unit/permission-description.test.ts +++ b/packages/kilo-vscode/tests/unit/permission-description.test.ts @@ -1,9 +1,11 @@ import { describe, test, expect } from "bun:test" import { describePatterns, + describeRule, resolveLabel, TOOL_LABEL_KEYS, } from "../../webview-ui/src/components/chat/permission-dock-utils" +import { resolveTemplate } from "../../webview-ui/src/context/language-utils" // Mock t() that returns the English label for known keys, or the key itself const labels: Record = { @@ -24,8 +26,10 @@ const labels: Record = { "ui.permission.toolLabel.task": "Task", "ui.permission.toolLabel.skill": "Skill", "ui.permission.toolLabel.lsp": "LSP", + "ui.permission.doomLoop.prompt": "Potential loop detected for the {{tool}} tool. Continue running?", + "ui.permission.doomLoop.rule": "Continue {{tool}} calls", } -const t = (key: string) => labels[key] ?? key +const t = (key: string, params?: Record) => resolveTemplate(labels[key] ?? key, params) describe("describePatterns", () => { test("returns null when patterns is empty", () => { @@ -95,6 +99,14 @@ describe("describePatterns", () => { expect(result).toEqual({ kind: "single", text: "Web Search query" }) }) + test("doom loop describes the repeated tool with its human-readable label", () => { + const result = describePatterns("doom_loop", ["read"], t) + expect(result).toEqual({ + kind: "single", + text: "Potential loop detected for the Read tool. Continue running?", + }) + }) + test("TOOL_LABEL_KEYS maps all expected tools", () => { const expected: Record = { read: "ui.permission.toolLabel.read", @@ -162,6 +174,17 @@ describe("describePatterns", () => { }) }) +describe("describeRule", () => { + test("describes doom loop rules as the action being allowed or denied", () => { + expect(describeRule("doom_loop", "read", t)).toBe("Continue Read calls") + }) + + test("preserves existing permission rule labels", () => { + expect(describeRule("read", "*", t)).toBe("Read") + expect(describeRule("read", "src/app.ts", t)).toBe("Read src/app.ts") + }) +}) + describe("resolveLabel", () => { test("returns translated label for known tool", () => { expect(resolveLabel("read", t)).toBe("Read") diff --git a/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts b/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts index 375a44efdc..523ba6f94d 100644 --- a/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts +++ b/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts @@ -1,5 +1,11 @@ import { describe, expect, it } from "bun:test" -import { connectProvider, disconnectProvider, fetchProviderData, saveCustomProvider } from "../../src/provider-actions" +import { + connectProvider, + disconnectProvider, + fetchProviderData, + resolveStoredKey, + saveCustomProvider, +} from "../../src/provider-actions" type ExistingGlobal = { disabled_providers?: string[]; provider?: Record } @@ -237,7 +243,7 @@ describe("saveCustomProvider", () => { expect(payload.myprovider.models["model-gone"]).toBeNull() }) - it("emits null sentinels for variants removed from a model that still exists", async () => { + it("emits null sentinels when reasoning and variants are removed from a model", async () => { const existing = { disabled_providers: [], provider: { @@ -264,11 +270,7 @@ describe("saveCustomProvider", () => { name: "My Provider", options: { baseURL: "https://example.com/v1" }, models: { - "model-1": { - name: "Model One", - reasoning: true, - variants: { high: { reasoningEffort: "high" } }, - }, + "model-1": { name: "Model One" }, }, } await saveCustomProvider(ctx, "req", "myprovider", next, undefined, false, null, setCachedConfig) @@ -277,11 +279,11 @@ describe("saveCustomProvider", () => { const model = ( calls.config[0].config.provider as Record< string, - { models: Record }> } + { models: Record }> } > ).myprovider.models["model-1"] - expect(model.variants).toBeDefined() - expect(model.variants?.high).toBeDefined() + expect(model.reasoning).toBeNull() + expect(model.variants?.high).toBeNull() expect(model.variants?.low).toBeNull() }) @@ -441,4 +443,69 @@ describe("fetchProviderData", () => { expect(result.authStates).toEqual({ "groq-test": "api" }) expect("key" in item).toBe(false) }) + + it("retains stripped keys for providers with a configured baseURL", async () => { + const client = { + provider: { + list: async () => ({ + data: { + all: [ + { + id: "myprovider", + name: "My Provider", + source: "config", + key: "sk-stored", + env: [], + options: { baseURL: "https://example.com/v1" }, + models: {}, + }, + { + id: "no-url", + name: "No URL", + source: "config", + key: "sk-other", + env: [], + models: {}, + }, + ], + connected: [], + default: {}, + }, + }), + auth: async () => ({ data: {} }), + }, + } as unknown as Parameters[0] + + const result = await fetchProviderData(client, "/tmp") + + expect(result.storedKeys).toEqual({ + myprovider: { key: "sk-stored", baseURL: "https://example.com/v1" }, + }) + expect(result.response.all.every((item) => !("key" in (item as Record)))).toBe(true) + }) +}) + +describe("resolveStoredKey", () => { + const storedKeys = { + myprovider: { key: "sk-stored", baseURL: "https://example.com/v1" }, + } + + it("returns the stored key when the fetch URL matches the configured baseURL", () => { + expect(resolveStoredKey(storedKeys, "myprovider", "https://example.com/v1")).toBe("sk-stored") + }) + + it("tolerates trailing-slash differences", () => { + expect(resolveStoredKey(storedKeys, "myprovider", "https://example.com/v1/")).toBe("sk-stored") + }) + + it("refuses to apply the stored key to a different host or path", () => { + expect(resolveStoredKey(storedKeys, "myprovider", "https://evil.example.net/v1")).toBeUndefined() + expect(resolveStoredKey(storedKeys, "myprovider", "https://example.com/v2")).toBeUndefined() + }) + + it("returns undefined for unknown or missing provider ids", () => { + expect(resolveStoredKey(storedKeys, "other", "https://example.com/v1")).toBeUndefined() + expect(resolveStoredKey(storedKeys, undefined, "https://example.com/v1")).toBeUndefined() + expect(resolveStoredKey(storedKeys, "", "https://example.com/v1")).toBeUndefined() + }) }) diff --git a/packages/kilo-vscode/tests/unit/question-handler.test.ts b/packages/kilo-vscode/tests/unit/question-handler.test.ts index 3353d6586a..c097b71a8a 100644 --- a/packages/kilo-vscode/tests/unit/question-handler.test.ts +++ b/packages/kilo-vscode/tests/unit/question-handler.test.ts @@ -1,104 +1,319 @@ -import { describe, expect, it } from "bun:test" -import type { KiloClient } from "@kilocode/sdk/v2/client" -import { handleQuestionReject, handleQuestionReply } from "../../src/kilo-provider/handlers/question" +import { describe, expect, it, spyOn } from "bun:test" +import type { QuestionRequest } from "@kilocode/sdk/v2/client" +import { + fetchAndSendPendingQuestions, + handleQuestionReject, + handleQuestionReply, + type QuestionContext, +} from "../../src/kilo-provider/handlers/question" -describe("question handlers", () => { - it("routes replies using the question session when provided", async () => { - const calls: Array> = [] - const client = { - question: { - reply: async (input: Record) => { - calls.push(input) - return true - }, - reject: async () => true, +function pending(id: string, sessionID: string): QuestionRequest { + return { + id, + sessionID, + questions: [ + { + header: "Continue", + question: "What next?", + options: [{ label: "Continue", description: "Keep going" }], }, - } as unknown as KiloClient + ], + blocking: false, + tool: undefined, + } +} - const ok = await handleQuestionReply( - { - client, - currentSessionId: "ses-root", - postMessage() {}, - getWorkspaceDirectory(sessionId) { - return sessionId ? `/repo/${sessionId}` : "/repo" - }, +function ctx( + opts: { + tracked?: string[] + dirs?: Map + extra?: string[] + pending?: Record + errors?: { list?: Record; reply?: unknown; reject?: unknown } + changeOnList?: string + removeOnList?: string + } = {}, +) { + const messages: unknown[] = [] + const queries: string[] = [] + const replies: unknown[] = [] + const rejects: unknown[] = [] + const questionDirs = new Map() + const dirs = opts.dirs ?? new Map() + let revision = 0 + let changed = false + const removed = new Set() + const client = { + question: { + list: async (args: { directory?: string }) => { + const dir = args.directory ?? "" + queries.push(dir) + if (opts.changeOnList === dir && !changed) { + changed = true + revision += 1 + } + const error = opts.errors?.list?.[dir] + if (error) return { data: undefined, error } + const data = opts.pending?.[dir] ?? [] + if (opts.removeOnList !== dir) return { data } + if (removed.has(dir)) return { data: [] } + removed.add(dir) + revision += 1 + return { data } + }, + reply: async (args: unknown) => { + replies.push(args) + if (opts.errors?.reply) throw opts.errors.reply + return { data: true } }, - "req-1", - [["Start new session"]], - "ses-worktree", - ) + reject: async (args: unknown) => { + rejects.push(args) + if (opts.errors?.reject) throw opts.errors.reject + return { data: true } + }, + }, + } as unknown as QuestionContext["client"] + const fake: QuestionContext = { + client, + currentSessionId: "ses-root", + trackedSessionIds: new Set(opts.tracked ?? ["ses-root"]), + sessionDirectories: dirs, + extraDirectories: () => opts.extra ?? [], + postMessage: (msg) => messages.push(msg), + getWorkspaceDirectory: (sessionID) => dirs.get(sessionID ?? "") ?? "/workspace", + recordQuestionDirectory: (id, dir) => questionDirs.set(id, dir), + getQuestionDirectory: (id) => questionDirs.get(id), + clearQuestionDirectory: (id) => { + questionDirs.delete(id) + revision += 1 + }, + getQuestionRevision: () => revision, + pruneQuestionDirectories: (active, scanned) => { + const size = questionDirs.size + for (const [id, dir] of questionDirs) { + if (active.has(id) || !scanned.has(dir)) continue + questionDirs.delete(id) + } + if (questionDirs.size !== size) revision += 1 + }, + } + return { fake, messages, queries, replies, rejects, questionDirs } +} + +describe("question handlers", () => { + it("routes replies through the recorded request directory", async () => { + const { fake, replies, questionDirs } = ctx({ + tracked: ["ses-worktree"], + dirs: new Map([["ses-worktree", "/workspace/.kilo/worktrees/current"]]), + }) + questionDirs.set("req-1", "/workspace/.kilo/worktrees/origin") + + const ok = await handleQuestionReply(fake, "req-1", [["Continue"]], "ses-worktree") expect(ok).toBe(true) - expect(calls).toEqual([ + expect(replies).toEqual([ { requestID: "req-1", - answers: [["Start new session"]], - directory: "/repo/ses-worktree", + answers: [["Continue"]], + directory: "/workspace/.kilo/worktrees/origin", }, ]) + expect(questionDirs.has("req-1")).toBe(false) }) - it("falls back to the current session when no question session is provided", async () => { - const calls: Array> = [] - const client = { - question: { - reply: async (input: Record) => { - calls.push(input) - return true - }, - reject: async () => true, - }, - } as unknown as KiloClient + it("routes rejects through the recorded request directory", async () => { + const { fake, rejects, questionDirs } = ctx({ + tracked: ["ses-worktree"], + dirs: new Map([["ses-worktree", "/workspace/.kilo/worktrees/current"]]), + }) + questionDirs.set("req-2", "/workspace/.kilo/worktrees/origin") - const ok = await handleQuestionReply( - { - client, - currentSessionId: "ses-root", - postMessage() {}, - getWorkspaceDirectory(sessionId) { - return sessionId ? `/repo/${sessionId}` : "/repo" - }, - }, - "req-2", - [["Continue here"]], - ) + const ok = await handleQuestionReject(fake, "req-2", "ses-worktree") expect(ok).toBe(true) - expect(calls[0]?.directory).toBe("/repo/ses-root") + expect(rejects).toEqual([{ requestID: "req-2", directory: "/workspace/.kilo/worktrees/origin" }]) + expect(questionDirs.has("req-2")).toBe(false) }) - it("routes rejects using the question session when provided", async () => { - const calls: Array> = [] - const client = { - question: { - reply: async () => true, - reject: async (input: Record) => { - calls.push(input) - return true - }, - }, - } as unknown as KiloClient + it("falls back to the question session directory when no request route is known", async () => { + const { fake, replies } = ctx({ + tracked: ["ses-worktree"], + dirs: new Map([["ses-worktree", "/workspace/.kilo/worktrees/current"]]), + }) - const ok = await handleQuestionReject( - { - client, - currentSessionId: "ses-root", - postMessage() {}, - getWorkspaceDirectory(sessionId) { - return sessionId ? `/repo/${sessionId}` : "/repo" - }, - }, - "req-3", - "ses-worktree", - ) + const ok = await handleQuestionReply(fake, "req-3", [["Continue"]], "ses-worktree") expect(ok).toBe(true) - expect(calls).toEqual([ + expect(replies).toEqual([ { requestID: "req-3", - directory: "/repo/ses-worktree", + answers: [["Continue"]], + directory: "/workspace/.kilo/worktrees/current", }, ]) }) + + it("removes a stale question when the backend reports it missing", async () => { + const error = new Error("Question request not found", { + cause: { status: 404, body: { name: "NotFoundError" } }, + }) + const { fake, messages, questionDirs } = ctx({ errors: { reply: error } }) + questionDirs.set("req-stale", "/workspace/.kilo/worktrees/origin") + + const ok = await handleQuestionReply(fake, "req-stale", [["Continue"]], "ses-root") + + expect(ok).toBe(false) + expect(questionDirs.has("req-stale")).toBe(false) + expect(messages).toContainEqual({ type: "questionResolved", requestID: "req-stale" }) + }) + + it("keeps fallback-directory 404s retryable while recovering the request route", async () => { + const error = new Error("Question request not found", { + cause: { status: 404, body: { name: "NotFoundError" } }, + }) + const dir = "/workspace/.kilo/worktrees/origin" + const { fake, messages, questionDirs } = ctx({ + tracked: ["ses-root"], + extra: [dir], + pending: { [dir]: [pending("req-misrouted", "ses-root")] }, + errors: { reply: error }, + }) + const spy = spyOn(console, "error").mockImplementation(() => {}) + + const ok = await handleQuestionReply(fake, "req-misrouted", [["Continue"]], "ses-root") + spy.mockRestore() + + expect(ok).toBe(false) + expect(messages).not.toContainEqual({ type: "questionResolved", requestID: "req-misrouted" }) + expect(messages).toContainEqual({ type: "questionError", requestID: "req-misrouted" }) + expect(questionDirs.get("req-misrouted")).toBe(dir) + }) + + it("removes a fallback question when recovery confirms it is stale", async () => { + const error = new Error("Question request not found", { + cause: { status: 404, body: { _tag: "NotFound" } }, + }) + const { fake, messages } = ctx({ errors: { reject: error } }) + + const ok = await handleQuestionReject(fake, "req-stale", "ses-root") + + expect(ok).toBe(false) + expect(messages).toContainEqual({ type: "questionResolved", requestID: "req-stale" }) + expect(messages).not.toContainEqual({ type: "questionError", requestID: "req-stale" }) + }) + + it("keeps fallback questions retryable when recovery is incomplete", async () => { + const error = new Error("Question request not found", { + cause: { status: 404, body: { _tag: "NotFound" } }, + }) + const dir = "/workspace/.kilo/worktrees/failing" + const { fake, messages } = ctx({ + extra: [dir], + errors: { list: { [dir]: new Error("temporary failure") }, reply: error }, + }) + const spy = spyOn(console, "error").mockImplementation(() => {}) + + const ok = await handleQuestionReply(fake, "req-unknown", [["Continue"]], "ses-root") + spy.mockRestore() + + expect(ok).toBe(false) + expect(messages).toContainEqual({ type: "questionError", requestID: "req-unknown" }) + expect(messages).not.toContainEqual({ type: "questionResolved", requestID: "req-unknown" }) + }) + + it("keeps non-404 failures retryable", async () => { + const error = new Error("Internal server error", { + cause: { status: 500, body: { name: "InternalServerError" } }, + }) + const { fake, messages, questionDirs } = ctx({ errors: { reject: error } }) + const spy = spyOn(console, "error").mockImplementation(() => {}) + questionDirs.set("req-error", "/workspace/.kilo/worktrees/origin") + + const ok = await handleQuestionReject(fake, "req-error", "ses-root") + spy.mockRestore() + + expect(ok).toBe(false) + expect(questionDirs.get("req-error")).toBe("/workspace/.kilo/worktrees/origin") + expect(messages).toContainEqual({ type: "questionError", requestID: "req-error" }) + }) +}) + +describe("question recovery", () => { + it("records the directory that owns each recovered question", async () => { + const dir = "/workspace/.kilo/worktrees/late" + const { fake, queries, messages, questionDirs } = ctx({ + tracked: ["ses-worktree"], + extra: [dir], + pending: { [dir]: [pending("req-1", "ses-worktree")] }, + }) + + await fetchAndSendPendingQuestions(fake) + + expect(queries).toEqual(["/workspace", dir]) + expect(questionDirs.get("req-1")).toBe(dir) + expect(messages).toContainEqual({ + type: "questionRequest", + question: pending("req-1", "ses-worktree"), + }) + }) + + it("deduplicates recovered questions across directories", async () => { + const item = pending("req-1", "ses-worktree") + const dir = "/workspace/.kilo/worktrees/feature" + const { fake, messages } = ctx({ + tracked: ["ses-worktree"], + dirs: new Map([["ses-worktree", dir]]), + pending: { "/workspace": [item], [dir]: [item] }, + }) + + await fetchAndSendPendingQuestions(fake) + + expect(messages).toHaveLength(1) + }) + + it("retries a recovery snapshot invalidated by a newer question event", async () => { + const item = pending("req-missed", "ses-root") + const { fake, messages, queries, questionDirs } = ctx({ + pending: { "/workspace": [item] }, + changeOnList: "/workspace", + }) + + await fetchAndSendPendingQuestions(fake) + + expect(queries).toEqual(["/workspace", "/workspace"]) + expect(messages).toContainEqual({ type: "questionRequest", question: item }) + expect(questionDirs.get("req-missed")).toBe("/workspace") + }) + + it("does not repost a question resolved during recovery", async () => { + const item = pending("req-resolved", "ses-root") + const { fake, messages, queries } = ctx({ + pending: { "/workspace": [item] }, + removeOnList: "/workspace", + }) + + await fetchAndSendPendingQuestions(fake) + + expect(queries).toEqual(["/workspace", "/workspace"]) + expect(messages).toEqual([]) + }) + + it("prunes scanned routes while preserving routes from failed directories", async () => { + const dir = "/workspace/.kilo/worktrees/failing" + const error = new Error("temporary failure") + const { fake, questionDirs } = ctx({ + tracked: ["ses-worktree"], + dirs: new Map([["ses-worktree", dir]]), + errors: { list: { [dir]: error } }, + }) + const spy = spyOn(console, "error").mockImplementation(() => {}) + questionDirs.set("workspace-stale", "/workspace") + questionDirs.set("worktree-pending", dir) + + await fetchAndSendPendingQuestions(fake) + spy.mockRestore() + + expect(questionDirs.has("workspace-stale")).toBe(false) + expect(questionDirs.get("worktree-pending")).toBe(dir) + }) }) diff --git a/packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts b/packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts index 83f03b0fa7..5be7489e9c 100644 --- a/packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts +++ b/packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts @@ -4,8 +4,18 @@ import path from "node:path" const ROOT = path.resolve(import.meta.dir, "../..") const TURN_FILE = path.join(ROOT, "webview-ui/src/components/chat/VscodeSessionTurn.tsx") +const PROVIDER_FILE = path.join(ROOT, "src/KiloProvider.ts") const src = fs.readFileSync(TURN_FILE, "utf-8") +const provider = fs.readFileSync(PROVIDER_FILE, "utf-8") + +function method(name: string, next: string) { + const start = provider.indexOf(` private async ${name}`) + const end = provider.indexOf(` private async ${next}`, start) + expect(start).toBeGreaterThan(-1) + expect(end).toBeGreaterThan(start) + return provider.slice(start, end) +} describe("message revert checkpoints", () => { it("keeps revert actions available after a session is already reverted", () => { @@ -18,3 +28,28 @@ describe("message revert checkpoints", () => { expect(src).not.toMatch(/data-revert-disabled=\{[\s\S]*?!session\.revert\(\)/) }) }) + +describe("revert session synchronization", () => { + it("keeps REST responses as the mutation result", () => { + const revert = method("handleRevertSession", "handleUnrevertSession") + const unrevert = method("handleUnrevertSession", "handleCompact") + + expect(revert).toContain("await this.client.session.revert") + expect(unrevert).toContain("await this.client.session.unrevert") + expect(revert).toContain('type: "sessionUpdated"') + expect(unrevert).toContain('type: "sessionUpdated"') + }) + + it("distinguishes partial sync patches from full bus snapshots", () => { + expect(provider).toMatch(/source: "sync"/) + expect(provider).toMatch( + /if \(event\.type === "session\.updated"\) return "source" in event && event\.source === "sync"/, + ) + expect(provider).toMatch( + /isLegacySyncEvent\(event\)\s*\? applySessionPatch\(this\.currentSession, event\.properties\.info\)\s*:\s*event\.properties\.info/, + ) + expect(provider).toMatch( + /isFullSessionUpdatedEvent\(event\)\s*\? \{ type: "sessionUpdated" as const, session: this\.sessionToWebview\(event\.properties\.info\) \}/, + ) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/session-status.test.ts b/packages/kilo-vscode/tests/unit/session-status.test.ts index 6247858752..f0b3af60ba 100644 --- a/packages/kilo-vscode/tests/unit/session-status.test.ts +++ b/packages/kilo-vscode/tests/unit/session-status.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test" -import { seedSessionStatuses, getBusySessionCount } from "../../src/session-status" +import { seedSessionStatuses } from "../../src/session-status" import type { SessionStatus } from "@kilocode/sdk/v2/client" /** @@ -196,23 +196,3 @@ describe("seedSessionStatuses", () => { expect(msgs).toEqual([{ type: "sessionStatus", sessionID: "confirmed", status: "busy" }]) }) }) - -// --------------------------------------------------------------------------- -// getBusySessionCount -// --------------------------------------------------------------------------- - -describe("getBusySessionCount", () => { - it("returns 0 for empty map", () => { - expect(getBusySessionCount(new Map())).toBe(0) - }) - - it("counts only busy entries, not idle or retry", () => { - const map = new Map([ - ["a", "busy"], - ["b", "idle"], - ["c", "retry"], - ["d", "busy"], - ]) - expect(getBusySessionCount(map)).toBe(2) - }) -}) diff --git a/packages/kilo-vscode/tests/unit/settings-io.test.ts b/packages/kilo-vscode/tests/unit/settings-io.test.ts index 4ed6aaa364..ee0b838d91 100644 --- a/packages/kilo-vscode/tests/unit/settings-io.test.ts +++ b/packages/kilo-vscode/tests/unit/settings-io.test.ts @@ -168,12 +168,23 @@ describe("parseImport", () => { }) it("preserves task subagent model and variant settings", () => { - const json = JSON.stringify({ subagent_model: "anthropic/claude-sonnet-4", subagent_variant: "high" }) + const json = JSON.stringify({ + subagent_model: "anthropic/claude-sonnet-4", + subagent_variant: "high", + subagent_variant_overrides: { + "anthropic/claude-sonnet-4": "max", + "openai/gpt-5": "xhigh", + }, + }) const result = parseImport(json) expect(result.ok).toBe(true) if (result.ok) { expect(result.config.subagent_model).toBe("anthropic/claude-sonnet-4") expect(result.config.subagent_variant).toBe("high") + expect(result.config.subagent_variant_overrides).toEqual({ + "anthropic/claude-sonnet-4": "max", + "openai/gpt-5": "xhigh", + }) } }) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx index 62cbcb56f2..14534929e1 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx @@ -17,7 +17,7 @@ import { Tooltip } from "@kilocode/kilo-ui/tooltip" import { useSession } from "../../context/session" import { useLanguage } from "../../context/language" import { useConfig } from "../../context/config" -import { describePatterns, resolveLabel, savedRuleStates, type RuleDecision } from "./permission-dock-utils" +import { describePatterns, describeRule, savedRuleStates, type RuleDecision } from "./permission-dock-utils" import { PermissionCommand } from "./PermissionCommand" import { PermissionDiff } from "./PermissionDiff" import { permissionDiffs } from "./permission-diff-utils" @@ -249,11 +249,7 @@ export const PermissionDock: Component<{ - {command() - ? label(rule) - : rule === "*" - ? resolveLabel(props.request.toolName, language.t) - : `${resolveLabel(props.request.toolName, language.t)} ${rule}`} + {command() ? label(rule) : describeRule(props.request.toolName, rule, language.t)} )} diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/permission-dock-utils.ts b/packages/kilo-vscode/webview-ui/src/components/chat/permission-dock-utils.ts index 8d726db290..efa04e468f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/permission-dock-utils.ts +++ b/packages/kilo-vscode/webview-ui/src/components/chat/permission-dock-utils.ts @@ -52,6 +52,17 @@ export function resolveLabel(tool: string, t: (key: string) => string): string { return key ? t(key) : tool } +export function describeRule( + tool: string, + rule: string, + t: (key: string, params?: Record) => string, +): string { + if (tool === "doom_loop") { + return t("ui.permission.doomLoop.rule", { tool: resolveLabel(rule, t) }) + } + return rule === "*" ? resolveLabel(tool, t) : `${resolveLabel(tool, t)} ${rule}` +} + /** * Build a human-readable description for a permission request's patterns. * @@ -62,13 +73,20 @@ export function resolveLabel(tool: string, t: (key: string) => string): string { export function describePatterns( tool: string, patterns: string[], - t: (key: string) => string, + t: (key: string, params?: Record) => string, ): PatternDescription | null { const filtered = patterns.filter((p) => p !== "*") if (filtered.length === 0) return null - const key = TOOL_LABEL_KEYS[tool] - const label = key ? t(key) : tool + // doom-loop requests always contain one repeated tool pattern. + if (tool === "doom_loop") { + return { + kind: "single", + text: t("ui.permission.doomLoop.prompt", { tool: resolveLabel(filtered[0], t) }), + } + } + + const label = resolveLabel(tool, t) if (filtered.length === 1) return { kind: "single", text: `${label} ${filtered[0]}` } return { kind: "multi", title: `${label}:`, paths: filtered } } diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx index dd61fa4940..63d45639c4 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx @@ -43,6 +43,8 @@ interface SelectOption { import SettingsRow from "./SettingsRow" +const builtin = (skill: SkillInfo) => skill.location === "builtin" || skill.location === "" + // View states for the agents subtab type AgentView = "list" | "create" | "edit" @@ -848,10 +850,10 @@ const AgentBehaviourTab: Component = () => { }} >
{skill.description}
- {skill.location !== "builtin" &&
{skill.location}
} + {!builtin(skill) &&
{skill.location}
} - {skill.location !== "builtin" && ( + {!builtin(skill) && ( confirmRemoveSkill(skill)} /> )} diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx index 2008b57b65..9b24cacf22 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx @@ -195,6 +195,12 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { const raw = fetchKey().trim() const env = raw.match(/^\{env:([^}]+)\}$/)?.[1]?.trim() const apiKey = raw && !env ? raw : undefined + // When editing an existing provider with the key field untouched, the + // webview has no key to send — keys are stripped before provider data + // reaches it. Send the providerID so the extension can authenticate the + // fetch with the stored key (#10139). Anything typed into the field + // (a key or {env:VAR} syntax) takes precedence. + const providerID = !raw && props.existing ? props.existing.providerID : undefined const existing = new Set(form.models.map((m) => m.id.trim()).filter(Boolean)) const hdrs = form.headers @@ -253,6 +259,7 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { requestId: rid, baseURL: url, apiKey, + providerID, headers, }) } diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx index a3fc83d1c2..4960afe249 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx @@ -1,4 +1,4 @@ -import { Component, For, createMemo } from "solid-js" +import { Component, For, Show, createMemo } from "solid-js" import { Card } from "@kilocode/kilo-ui/card" import { useConfig } from "../../context/config" import { useLanguage } from "../../context/language" @@ -36,15 +36,14 @@ const ModelsTab: Component = () => { } const subagentModel = createMemo(() => parseModelString(config().subagent_model ?? undefined)) - const subagentVariants = createMemo(() => { - const model = provider.findModel(subagentModel()) - return model?.variants ? Object.keys(model.variants) : [] - }) + const variantKey = createMemo(() => config().subagent_model ?? undefined) + const subagentVariants = createMemo(() => Object.keys(provider.findModel(subagentModel())?.variants ?? {})) const subagentVariant = createMemo(() => { - const list = subagentVariants() - if (list.length === 0) return undefined - const value = config().subagent_variant ?? undefined - return value && list.includes(value) ? value : undefined + const key = variantKey() + if (!key) return undefined + const value = config().subagent_variant_overrides?.[key] + if (value) return value + return config().subagent_model === key ? (config().subagent_variant ?? undefined) : undefined }) function handleSubagentModelSelect(providerID: string, modelID: string) { @@ -52,16 +51,20 @@ const ModelsTab: Component = () => { updateConfig({ subagent_model: null, subagent_variant: null }) return } - const model = { providerID, modelID } - const variants = provider.findModel(model)?.variants - const list = variants ? Object.keys(variants) : [] - const value = config().subagent_model === `${providerID}/${modelID}` ? config().subagent_variant : undefined - const variant = value && list.includes(value) ? value : list[0] - updateConfig({ subagent_model: `${providerID}/${modelID}`, subagent_variant: variant ?? null }) + const value = `${providerID}/${modelID}` + updateConfig({ + subagent_model: value, + ...(config().subagent_model === value ? {} : { subagent_variant: null }), + }) } - function handleSubagentVariantSelect(value: string) { - updateConfig({ subagent_variant: value }) + function updateSubagentVariant(value: string | null) { + const key = variantKey() + if (!key) return + updateConfig({ + subagent_variant_overrides: { [key]: value }, + ...(config().subagent_model === key ? { subagent_variant: null } : {}), + }) } const allAgents = createMemo(() => session.agents()) @@ -124,7 +127,7 @@ const ModelsTab: Component = () => { title={language.t("settings.providers.subagentModel.title")} description={language.t("settings.providers.subagentModel.description")} > -
+
{ label={language.t("settings.providers.subagentModel.title")} description={language.t("settings.providers.subagentModel.description")} /> - + 0}> + updateSubagentVariant(value)} + onClear={() => updateSubagentVariant(null)} + allowClear + clearLabel={language.t("settings.providers.notSet")} + placement="bottom-start" + globalTrigger={false} + /> +
= [ "small_model", "subagent_model", "subagent_variant", + "subagent_variant_overrides", "default_agent", "agent", "provider", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index b0084e40d4..8782325613 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -522,6 +522,8 @@ export const dict = { "notification.permission.titleSubagent": "مطلوب إذن (وكيل فرعي)", "notification.permission.description": "{{sessionTitle}} في {{projectName}} يحتاج إلى إذن", "ui.permission.manageAutoApprove": "إدارة قواعد الموافقة التلقائية", + "ui.permission.doomLoop.prompt": "تم اكتشاف حلقة محتملة في أداة {{tool}}. هل تريد متابعة التشغيل؟", + "ui.permission.doomLoop.rule": "متابعة استدعاءات {{tool}}", "ui.permission.rule.addToAllowed": "أضف إلى قائمة المسموح", "ui.permission.rule.removeFromAllowed": "أزل من قائمة المسموح", "ui.permission.rule.addToDenied": "أضف إلى قائمة المرفوض", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index ae04ef3315..2da2912caa 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -527,6 +527,8 @@ export const dict = { "notification.permission.titleSubagent": "Permissão necessária (subagente)", "notification.permission.description": "{{sessionTitle}} em {{projectName}} precisa de permissão", "ui.permission.manageAutoApprove": "Gerenciar regras de aprovação automática", + "ui.permission.doomLoop.prompt": "Possível loop detectado na ferramenta {{tool}}. Continuar executando?", + "ui.permission.doomLoop.rule": "Continuar chamadas de {{tool}}", "ui.permission.rule.addToAllowed": "Adicionar à lista de permitidos", "ui.permission.rule.removeFromAllowed": "Remover da lista de permitidos", "ui.permission.rule.addToDenied": "Adicionar à lista de negados", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index b6e5725db7..79f9d982cf 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -530,6 +530,8 @@ export const dict = { "notification.permission.titleSubagent": "Potrebna dozvola (podagent)", "notification.permission.description": "{{sessionTitle}} u {{projectName}} traži dozvolu", "ui.permission.manageAutoApprove": "Upravljanje pravilima automatskog odobravanja", + "ui.permission.doomLoop.prompt": "Otkrivena je moguća petlja za alat {{tool}}. Nastaviti izvršavanje?", + "ui.permission.doomLoop.rule": "Nastavi pozive alata {{tool}}", "ui.permission.rule.addToAllowed": "Dodaj na listu dozvoljenih", "ui.permission.rule.removeFromAllowed": "Ukloni sa liste dozvoljenih", "ui.permission.rule.addToDenied": "Dodaj na listu odbijenih", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 241e3c4511..d4390284ac 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -527,6 +527,8 @@ export const dict = { "notification.permission.titleSubagent": "Tilladelse påkrævet (underagent)", "notification.permission.description": "{{sessionTitle}} i {{projectName}} kræver tilladelse", "ui.permission.manageAutoApprove": "Administrer regler for automatisk godkendelse", + "ui.permission.doomLoop.prompt": "Der blev registreret en mulig løkke for værktøjet {{tool}}. Fortsæt kørslen?", + "ui.permission.doomLoop.rule": "Fortsæt {{tool}}-kald", "ui.permission.rule.addToAllowed": "Tilføj til tilladelseslisten", "ui.permission.rule.removeFromAllowed": "Fjern fra tilladelseslisten", "ui.permission.rule.addToDenied": "Tilføj til afvisningslisten", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 04ffc2f2c1..e4e5627d00 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -534,6 +534,8 @@ export const dict = { "notification.permission.titleSubagent": "Berechtigung erforderlich (Subagent)", "notification.permission.description": "{{sessionTitle}} in {{projectName}} benötigt Berechtigung", "ui.permission.manageAutoApprove": "Regeln für automatische Genehmigung verwalten", + "ui.permission.doomLoop.prompt": "Potenzielle Schleife beim Tool {{tool}} erkannt. Weiter ausführen?", + "ui.permission.doomLoop.rule": "{{tool}}-Aufrufe fortsetzen", "ui.permission.rule.addToAllowed": "Zur Erlaubt-Liste hinzufügen", "ui.permission.rule.removeFromAllowed": "Aus der Erlaubt-Liste entfernen", "ui.permission.rule.addToDenied": "Zur Verweigert-Liste hinzufügen", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index affab854d3..86b8aa7893 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -527,6 +527,8 @@ export const dict = { "notification.permission.titleSubagent": "Permission required (subagent)", "notification.permission.description": "{{sessionTitle}} in {{projectName}} needs permission", "ui.permission.manageAutoApprove": "Manage Auto-Approve Rules", + "ui.permission.doomLoop.prompt": "Potential loop detected for the {{tool}} tool. Continue running?", + "ui.permission.doomLoop.rule": "Continue {{tool}} calls", "ui.permission.rule.addToAllowed": "Add to allowed list", "ui.permission.rule.removeFromAllowed": "Remove from allowed list", "ui.permission.rule.addToDenied": "Add to denied list", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 68d58c4c58..d756101b4a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -531,6 +531,8 @@ export const dict = { "notification.permission.titleSubagent": "Permiso requerido (subagente)", "notification.permission.description": "{{sessionTitle}} en {{projectName}} necesita permiso", "ui.permission.manageAutoApprove": "Gestionar reglas de aprobación automática", + "ui.permission.doomLoop.prompt": "Se detectó un posible bucle en la herramienta {{tool}}. ¿Continuar ejecutando?", + "ui.permission.doomLoop.rule": "Continuar llamadas a {{tool}}", "ui.permission.rule.addToAllowed": "Añadir a la lista de permitidos", "ui.permission.rule.removeFromAllowed": "Quitar de la lista de permitidos", "ui.permission.rule.addToDenied": "Añadir a la lista de denegados", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 382d972c4d..58059b7b5e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -536,6 +536,8 @@ export const dict = { "notification.permission.titleSubagent": "Permission requise (sous-agent)", "notification.permission.description": "{{sessionTitle}} dans {{projectName}} a besoin d'une permission", "ui.permission.manageAutoApprove": "Gérer les règles d'approbation automatique", + "ui.permission.doomLoop.prompt": "Boucle potentielle détectée pour l’outil {{tool}}. Continuer l’exécution ?", + "ui.permission.doomLoop.rule": "Continuer les appels à {{tool}}", "ui.permission.rule.addToAllowed": "Ajouter à la liste des autorisés", "ui.permission.rule.removeFromAllowed": "Retirer de la liste des autorisés", "ui.permission.rule.addToDenied": "Ajouter à la liste des refusés", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/it.ts b/packages/kilo-vscode/webview-ui/src/i18n/it.ts index 4656f1e895..ba297b9c18 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/it.ts @@ -448,6 +448,8 @@ export const dict = { "notification.permission.titleSubagent": "Autorizzazione richiesta (sub-agent)", "notification.permission.description": "{{sessionTitle}} in {{projectName}} richiede autorizzazione", "ui.permission.manageAutoApprove": "Gestisci regole approvazione automatica", + "ui.permission.doomLoop.prompt": "Rilevato un potenziale ciclo nello strumento {{tool}}. Continuare l'esecuzione?", + "ui.permission.doomLoop.rule": "Continua le chiamate a {{tool}}", "ui.permission.rule.addToAllowed": "Aggiungi alla lista consentiti", "ui.permission.rule.removeFromAllowed": "Rimuovi dalla lista consentiti", "ui.permission.rule.addToDenied": "Aggiungi alla lista negati", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 277ea1a10b..ca2ce756fe 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -524,6 +524,8 @@ export const dict = { "notification.permission.titleSubagent": "権限が必要です(サブエージェント)", "notification.permission.description": "{{projectName}} の {{sessionTitle}} が権限を必要としています", "ui.permission.manageAutoApprove": "自動承認ルールを管理", + "ui.permission.doomLoop.prompt": "{{tool}} ツールでループの可能性が検出されました。実行を続行しますか?", + "ui.permission.doomLoop.rule": "{{tool}} の呼び出しを続行", "ui.permission.rule.addToAllowed": "許可リストに追加", "ui.permission.rule.removeFromAllowed": "許可リストから削除", "ui.permission.rule.addToDenied": "拒否リストに追加", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index fdaf39d51d..d679e27bee 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -525,6 +525,8 @@ export const dict = { "notification.permission.titleSubagent": "권한 필요 (서브에이전트)", "notification.permission.description": "{{projectName}}의 {{sessionTitle}}에서 권한이 필요합니다", "ui.permission.manageAutoApprove": "자동 승인 규칙 관리", + "ui.permission.doomLoop.prompt": "{{tool}} 도구에서 잠재적인 반복 실행이 감지되었습니다. 계속 실행하시겠습니까?", + "ui.permission.doomLoop.rule": "{{tool}} 호출 계속", "ui.permission.rule.addToAllowed": "허용 목록에 추가", "ui.permission.rule.removeFromAllowed": "허용 목록에서 제거", "ui.permission.rule.addToDenied": "거부 목록에 추가", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index 42f413e7f7..14a2cc42b8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -530,6 +530,8 @@ export const dict = { "notification.permission.titleSubagent": "Toestemming vereist (subagent)", "notification.permission.description": "{{sessionTitle}} in {{projectName}} heeft toestemming nodig", "ui.permission.manageAutoApprove": "Beheer automatisch goedkeuren regels", + "ui.permission.doomLoop.prompt": "Mogelijke lus gedetecteerd voor het hulpmiddel {{tool}}. Doorgaan met uitvoeren?", + "ui.permission.doomLoop.rule": "Doorgaan met {{tool}}-aanroepen", "ui.permission.rule.addToAllowed": "Toevoegen aan toegestane lijst", "ui.permission.rule.removeFromAllowed": "Verwijderen van toegestane lijst", "ui.permission.rule.addToDenied": "Toevoegen aan geweigerde lijst", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 8c5db2c95c..3e1b704e95 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -531,6 +531,8 @@ export const dict = { "notification.permission.titleSubagent": "Tillatelse påkrevd (underagent)", "notification.permission.description": "{{sessionTitle}} i {{projectName}} trenger tillatelse", "ui.permission.manageAutoApprove": "Administrer regler for automatisk godkjenning", + "ui.permission.doomLoop.prompt": "Mulig løkke oppdaget for verktøyet {{tool}}. Fortsette kjøringen?", + "ui.permission.doomLoop.rule": "Fortsett {{tool}}-kall", "ui.permission.rule.addToAllowed": "Legg til i tillatt-listen", "ui.permission.rule.removeFromAllowed": "Fjern fra tillatt-listen", "ui.permission.rule.addToDenied": "Legg til i blokkert-listen", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 367b7e5dbd..77b8f2160b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -529,6 +529,8 @@ export const dict = { "notification.permission.titleSubagent": "Wymagane uprawnienie (podagent)", "notification.permission.description": "{{sessionTitle}} w {{projectName}} potrzebuje uprawnienia", "ui.permission.manageAutoApprove": "Zarządzaj regułami automatycznego zatwierdzania", + "ui.permission.doomLoop.prompt": "Wykryto potencjalną pętlę dla narzędzia {{tool}}. Kontynuować działanie?", + "ui.permission.doomLoop.rule": "Kontynuuj wywołania {{tool}}", "ui.permission.rule.addToAllowed": "Dodaj do listy dozwolonych", "ui.permission.rule.removeFromAllowed": "Usuń z listy dozwolonych", "ui.permission.rule.addToDenied": "Dodaj do listy zablokowanych", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index c84a94b078..61cc7872f0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -529,6 +529,9 @@ export const dict = { "notification.permission.titleSubagent": "Требуется разрешение (субагент)", "notification.permission.description": "{{sessionTitle}} в {{projectName}} требуется разрешение", "ui.permission.manageAutoApprove": "Управление правилами автоодобрения", + "ui.permission.doomLoop.prompt": + "Обнаружен потенциальный цикл при работе инструмента {{tool}}. Продолжить выполнение?", + "ui.permission.doomLoop.rule": "Продолжать вызовы {{tool}}", "ui.permission.rule.addToAllowed": "Добавить в список разрешённых", "ui.permission.rule.removeFromAllowed": "Убрать из списка разрешённых", "ui.permission.rule.addToDenied": "Добавить в список запрещённых", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 2646e88a69..bc3bcdd18e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -524,6 +524,8 @@ export const dict = { "notification.permission.titleSubagent": "ต้องการสิทธิ์ (ตัวแทนย่อย)", "notification.permission.description": "{{sessionTitle}} ใน {{projectName}} ต้องการสิทธิ์", "ui.permission.manageAutoApprove": "จัดการกฎการอนุมัติอัตโนมัติ", + "ui.permission.doomLoop.prompt": "ตรวจพบการวนซ้ำที่อาจเกิดขึ้นในเครื่องมือ {{tool}} ต้องการดำเนินการต่อหรือไม่", + "ui.permission.doomLoop.rule": "เรียกใช้ {{tool}} ต่อไป", "ui.permission.rule.addToAllowed": "เพิ่มไปยังรายการที่อนุญาต", "ui.permission.rule.removeFromAllowed": "นำออกจากรายการที่อนุญาต", "ui.permission.rule.addToDenied": "เพิ่มไปยังรายการที่ถูกปฏิเสธ", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index b4331e0522..0925c62031 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -529,6 +529,8 @@ export const dict = { "notification.permission.titleSubagent": "İzin gerekli (alt ajan)", "notification.permission.description": "{{projectName}} içindeki {{sessionTitle}} izin gerektiriyor", "ui.permission.manageAutoApprove": "Otomatik Onay Kurallarını Yönet", + "ui.permission.doomLoop.prompt": "{{tool}} aracında olası bir döngü algılandı. Çalıştırmaya devam edilsin mi?", + "ui.permission.doomLoop.rule": "{{tool}} çağrılarına devam et", "ui.permission.rule.addToAllowed": "İzin listesine ekle", "ui.permission.rule.removeFromAllowed": "İzin listesinden kaldır", "ui.permission.rule.addToDenied": "Engel listesine ekle", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index 31bd1c5a61..3ff24e78ca 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -531,6 +531,9 @@ export const dict = { "notification.permission.titleSubagent": "Потрібен дозвіл (підагент)", "notification.permission.description": "{{sessionTitle}} у {{projectName}} потребує дозволу", "ui.permission.manageAutoApprove": "Керувати правилами автоматичного схвалення", + "ui.permission.doomLoop.prompt": + "Виявлено потенційний цикл під час роботи інструмента {{tool}}. Продовжити виконання?", + "ui.permission.doomLoop.rule": "Продовжувати виклики {{tool}}", "ui.permission.rule.addToAllowed": "Додати до дозволених", "ui.permission.rule.removeFromAllowed": "Видалити з дозволених", "ui.permission.rule.addToDenied": "Додати до заблокованих", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index c186582fd0..8470cde2a2 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -518,6 +518,8 @@ export const dict = { "notification.permission.titleSubagent": "需要权限(子代理)", "notification.permission.description": "{{sessionTitle}}({{projectName}})需要权限", "ui.permission.manageAutoApprove": "管理自动审批规则", + "ui.permission.doomLoop.prompt": "检测到 {{tool}} 工具可能陷入循环。是否继续运行?", + "ui.permission.doomLoop.rule": "继续调用 {{tool}}", "ui.permission.rule.addToAllowed": "添加到允许列表", "ui.permission.rule.removeFromAllowed": "从允许列表移除", "ui.permission.rule.addToDenied": "添加到拒绝列表", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index e9d977f7c0..edda8ac724 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -518,6 +518,8 @@ export const dict = { "notification.permission.titleSubagent": "需要權限(子代理)", "notification.permission.description": "{{sessionTitle}}({{projectName}})需要權限", "ui.permission.manageAutoApprove": "管理自動核准規則", + "ui.permission.doomLoop.prompt": "偵測到 {{tool}} 工具可能陷入迴圈。是否繼續執行?", + "ui.permission.doomLoop.rule": "繼續呼叫 {{tool}}", "ui.permission.rule.addToAllowed": "加入允許清單", "ui.permission.rule.removeFromAllowed": "從允許清單移除", "ui.permission.rule.addToDenied": "加入拒絕清單", diff --git a/packages/kilo-vscode/webview-ui/src/styles/settings.css b/packages/kilo-vscode/webview-ui/src/styles/settings.css index d6371643cc..fac12d5d01 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/settings.css +++ b/packages/kilo-vscode/webview-ui/src/styles/settings.css @@ -107,6 +107,11 @@ & > * { max-width: 100%; } + + [data-slot="popover-trigger"] { + min-width: 0; + max-width: 100%; + } } } diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/config.ts b/packages/kilo-vscode/webview-ui/src/types/messages/config.ts index 21b156edce..74022ce09a 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/config.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/config.ts @@ -116,6 +116,7 @@ export interface Config { small_model?: string | null subagent_model?: string | null subagent_variant?: string | null + subagent_variant_overrides?: Record | null default_agent?: string | null agent?: Record provider?: Record diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts index ee7ffcc40c..15b73c3fda 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts @@ -971,6 +971,13 @@ export interface FetchCustomProviderModelsMessage { requestId: string baseURL: string apiKey?: string + /** + * When editing an existing provider and the key field is untouched, the + * webview has no key to send (keys are stripped before they reach it). + * It sends the providerID instead so the extension can authenticate the + * fetch with the stored key — which never crosses into the webview. + */ + providerID?: string headers?: Record } diff --git a/packages/opencode/CHANGELOG.md b/packages/opencode/CHANGELOG.md index b9ffc625d1..d1caadd0ba 100644 --- a/packages/opencode/CHANGELOG.md +++ b/packages/opencode/CHANGELOG.md @@ -1,5 +1,19 @@ # @kilocode/cli +## 7.3.46 + +### Patch Changes + +- [#11184](https://github.com/Kilo-Org/kilocode/pull/11184) [`adf03a9`](https://github.com/Kilo-Org/kilocode/commit/adf03a98245e8877c580cb1f77a7e0ea4f0af61d) - Support model-specific reasoning overrides for task subagents, including custom subagents with their own model and variant settings. + +- [#11178](https://github.com/Kilo-Org/kilocode/pull/11178) [`f63e771`](https://github.com/Kilo-Org/kilocode/commit/f63e77153cde1d9f1c3bf62e5aa543c07bf5f506) - Accelerate initial snapshots for regular Git sessions while preserving existing changes and asynchronously storing snapshots independently from the source repository. + +- Restore Kilo branding, fork-specific CLI commands, and CLI lifecycle initialization after upstream merges. + +- [#11240](https://github.com/Kilo-Org/kilocode/pull/11240) [`f820e57`](https://github.com/Kilo-Org/kilocode/commit/f820e57bab6c1ddd26f73964160bee7134488b96) - Prevent skill removal from recursively deleting working directories. + +- [#11179](https://github.com/Kilo-Org/kilocode/pull/11179) [`96a1610`](https://github.com/Kilo-Org/kilocode/commit/96a16102b2a6c22f0860641d7f78c076835c0c99) - Validate GitHub attachments and language server release paths before downloading or executing them. + ## 7.3.45 ### Patch Changes diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index a9d211411e..6fa28fce90 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -396,6 +396,7 @@ for (const item of targets) { { name, version: Script.version, + preferUnplugged: true, os: [item.os], cpu: [item.arch], keywords: pkg.keywords, // kilocode_change diff --git a/packages/opencode/script/postinstall.mjs b/packages/opencode/script/postinstall.mjs index 9694b29d27..d158c64525 100644 --- a/packages/opencode/script/postinstall.mjs +++ b/packages/opencode/script/postinstall.mjs @@ -1,14 +1,15 @@ #!/usr/bin/env node +import childProcess from "child_process" import fs from "fs" -import path from "path" import os from "os" -import childProcess from "child_process" -import { fileURLToPath } from "url" +import path from "path" import { createRequire } from "module" +import { fileURLToPath } from "url" const __dirname = path.dirname(fileURLToPath(import.meta.url)) const require = createRequire(import.meta.url) +const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8")) // kilocode_change start - variant detection matching bin/kilo logic const platformMap = { @@ -22,14 +23,13 @@ const archMap = { arm: "arm", } -function detectPlatformAndArch() { - const platform = platformMap[os.platform()] || os.platform() - const arch = archMap[os.arch()] || os.arch() - return { platform, arch } -} +const platform = platformMap[os.platform()] ?? os.platform() +const arch = archMap[os.arch()] ?? os.arch() +const base = `@kilocode/cli-${platform}-${arch}` +const sourceBinary = platform === "windows" ? "kilo.exe" : "kilo" +const targetBinary = path.join(__dirname, "bin", ".kilo") function supportsAvx2() { - const { platform, arch } = detectPlatformAndArch() if (arch !== "x64") return false if (platform === "linux") { @@ -53,125 +53,152 @@ function supportsAvx2() { } } + if (platform === "windows") { + const command = + '(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)' + + for (const executable of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) { + try { + const result = childProcess.spawnSync(executable, ["-NoProfile", "-NonInteractive", "-Command", command], { + encoding: "utf8", + timeout: 3000, + windowsHide: true, + }) + if (result.status !== 0) continue + const output = (result.stdout || "").trim().toLowerCase() + if (output === "true" || output === "1") return true + if (output === "false" || output === "0") return false + } catch { + continue + } + } + } + return false } function isMusl() { + if (platform !== "linux") return false + try { if (fs.existsSync("/etc/alpine-release")) return true } catch { - // ignore + // Ignore filesystem probes that are blocked by the host. } try { const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" }) - const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase() - if (text.includes("musl")) return true + return `${result.stdout || ""}${result.stderr || ""}`.toLowerCase().includes("musl") } catch { - // ignore + return false } - - return false } -function getPackageNames() { - const { platform, arch } = detectPlatformAndArch() - const base = `@kilocode/cli-${platform}-${arch}` - const avx2 = supportsAvx2() - const baseline = arch === "x64" && !avx2 +function packageNames() { + const baseline = arch === "x64" && !supportsAvx2() if (platform === "linux") { - const musl = isMusl() - if (musl) { - if (arch === "x64") { - if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base] - return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`] - } + if (isMusl()) { + if (arch === "x64") + return baseline + ? [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base] + : [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`] return [`${base}-musl`, base] } - if (arch === "x64") { - if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`] - return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`] - } + + if (arch === "x64") + return baseline + ? [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`] + : [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`] return [base, `${base}-musl`] } - if (arch === "x64") { - if (baseline) return [`${base}-baseline`, base] - return [base, `${base}-baseline`] - } + if (arch === "x64") return baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`] return [base] } -function findBinary() { - const { platform } = detectPlatformAndArch() - const binaryName = platform === "windows" ? "kilo.exe" : "kilo" - const names = getPackageNames() - - for (const packageName of names) { - try { - const packageJsonPath = require.resolve(`${packageName}/package.json`) - const packageDir = path.dirname(packageJsonPath) - const binaryPath = path.join(packageDir, "bin", binaryName) - - if (fs.existsSync(binaryPath)) { - return { binaryPath, binaryName } - } - } catch { - // package not installed, try next variant - } - } - - throw new Error(`Could not find any binary package. Tried: ${names.map((n) => `"${n}"`).join(", ")}`) +function resolveBinary(name) { + const packageJsonPath = require.resolve(`${name}/package.json`) + const binaryPath = path.join(path.dirname(packageJsonPath), "bin", sourceBinary) + if (!fs.existsSync(binaryPath)) throw new Error(`Binary not found at ${binaryPath}`) + return binaryPath } // kilocode_change end // kilocode_change start - copy runtime resources next to cached binary -function copyTreeSitterResources(binaryPath) { - const source = path.join(path.dirname(binaryPath), "tree-sitter") - const target = path.join(__dirname, "bin", "tree-sitter") - const runtime = path.join(source, "tree-sitter.wasm") - - if (!fs.existsSync(runtime)) return - - fs.rmSync(target, { recursive: true, force: true }) - fs.cpSync(source, target, { recursive: true }) +function copyResources(source) { + for (const [name, entry] of [ + ["tree-sitter", "tree-sitter.wasm"], + ["console", "index.html"], + ]) { + const dir = path.join(path.dirname(source), name) + if (!fs.existsSync(path.join(dir, entry))) continue + const target = path.join(__dirname, "bin", name) + fs.rmSync(target, { recursive: true, force: true }) + fs.cpSync(dir, target, { recursive: true }) + } } -function copyConsoleResources(binaryPath) { - const source = path.join(path.dirname(binaryPath), "console") - const target = path.join(__dirname, "bin", "console") - const index = path.join(source, "index.html") - - if (!fs.existsSync(index)) return - - fs.rmSync(target, { recursive: true, force: true }) - fs.cpSync(source, target, { recursive: true }) +function copyBinary(source) { + if (!fs.existsSync(source)) throw new Error(`Binary not found at ${source}`) + fs.mkdirSync(path.dirname(targetBinary), { recursive: true }) + if (fs.existsSync(targetBinary)) fs.unlinkSync(targetBinary) + try { + fs.linkSync(source, targetBinary) + } catch { + fs.copyFileSync(source, targetBinary) + } + copyResources(source) + fs.chmodSync(targetBinary, 0o755) } // kilocode_change end +function verifyBinary() { + const result = childProcess.spawnSync(targetBinary, ["--version"], { + stdio: "ignore", + windowsHide: true, + }) + return result.status === 0 +} + function main() { - if (os.platform() === "win32") { - // On Windows, the .exe is already included in the package and bin field points to it - console.log("Windows detected: binary setup not needed (using packaged .exe)") + if (platform === "windows") { + console.log("Windows detected: binary setup not needed (using packaged wrapper)") return } - const { binaryPath } = findBinary() - const target = path.join(__dirname, "bin", ".kilo") // kilocode_change - if (fs.existsSync(target)) fs.unlinkSync(target) - try { - fs.linkSync(binaryPath, target) - } catch { - fs.copyFileSync(binaryPath, target) + for (const name of packageNames()) { + try { + copyBinary(resolveBinary(name)) + if (verifyBinary()) return + } catch { + const temp = fs.mkdtempSync(path.join(os.tmpdir(), "kilo-install-")) + try { + const version = packageJson.optionalDependencies?.[name] + if (!version) continue + const result = childProcess.spawnSync( + "npm", + ["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${name}@${version}`], + { stdio: "inherit", windowsHide: true }, + ) + if (result.status !== 0) continue + copyBinary(path.join(temp, "node_modules", name, "bin", sourceBinary)) + if (verifyBinary()) return + } finally { + fs.rmSync(temp, { recursive: true, force: true }) + } + } } - copyTreeSitterResources(binaryPath) // kilocode_change - copyConsoleResources(binaryPath) // kilocode_change - fs.chmodSync(target, 0o755) + + throw new Error( + `It seems your package manager failed to install the right Kilo CLI package. Try manually installing ${packageNames() + .map((name) => JSON.stringify(name)) + .join(" or ")}.`, + ) } try { - void main() + main() } catch (error) { console.error("Failed to setup kilo binary:", error.message) process.exit(1) diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts index 126f43a34c..0d9ab24e2d 100755 --- a/packages/opencode/script/publish.ts +++ b/packages/opencode/script/publish.ts @@ -50,12 +50,14 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write( // kilocode_change end }, scripts: { - postinstall: "bun ./postinstall.mjs || node ./postinstall.mjs", + postinstall: "node ./postinstall.mjs", }, version: version, license: pkg.license, keywords: pkg.keywords, // kilocode_change private: pkg.private, // kilocode_change + os: ["darwin", "linux", "win32"], + cpu: ["arm64", "x64"], optionalDependencies: binaries, // kilocode_change start repository: { diff --git a/packages/opencode/specs/effect/errors.md b/packages/opencode/specs/effect/errors.md index 69298bde5c..fe526c2faa 100644 --- a/packages/opencode/specs/effect/errors.md +++ b/packages/opencode/specs/effect/errors.md @@ -70,11 +70,54 @@ Endpoint definitions declare which public errors can be emitted. Public HTTP error schemas carry their response status with `httpApiStatus` or the equivalent HttpApi schema annotation. +Effect's own HttpApi examples follow this pattern: + +```ts +export class Unauthorized extends Schema.TaggedErrorClass()( + "Unauthorized", + { message: Schema.String }, + { httpApiStatus: 401 }, +) {} + +export class Authorization extends HttpApiMiddleware.Service< + Authorization, + { + provides: CurrentUser + } +>()("app/Authorization", { + security: { bearer: HttpApiSecurity.bearer }, + error: Unauthorized, +}) {} +``` + +Endpoint-level errors use the same idea: + +```ts +export class ConfigApiError extends Schema.ErrorClass("ConfigApiError")( + { + name: Schema.Union(Schema.Literal("ConfigInvalidError"), Schema.Literal("ConfigJsonError")), + data: Schema.Struct({ message: Schema.optional(Schema.String), path: Schema.String }), + }, + { httpApiStatus: 400 }, +) {} + +HttpApiEndpoint.get("get", "/config", { + success: Config.Info, + error: ConfigApiError, +}) +``` + The service error and HTTP error may be the same class only when the wire shape is intentionally public. Use separate HTTP error schemas when the service error contains internals, low-level causes, retry hints, or data that should not be exposed to API clients. +Do not map every domain error into one universal HTTP error class. Prefer a +small public error vocabulary by route group: shared shapes like +`ApiNotFoundError`, route-specific shapes like `ConfigApiError`, and built-in +empty `HttpApiError.*` only when an empty/no-content body is the intended SDK +contract. + ## Mapping Guidance - Keep one-off translations inline in the handler. @@ -86,6 +129,35 @@ that should not be exposed to API clients. breaking API change. - Use built-in `HttpApiError.*` only when its generated body and SDK surface are intentionally the public contract. +- Prefer `Schema.ErrorClass` for public HTTP error bodies whose wire shape is + not the same as the internal domain error shape. +- Prefer `Schema.TaggedErrorClass` for service/domain errors and middleware + errors that are naturally tagged by `_tag`. +- If preserving a legacy `{ name, data }` body, model that shape explicitly in + the public API error schema instead of relying on `NamedError.toObject()` in + generic middleware. + +## User-Facing Rendering + +HTTP serialization and user rendering are separate boundaries. The server +should send structured public errors; CLI and TUI code should format those +structures through one shared formatter. + +For SDK calls using `{ throwOnError: true }`, the generated client may wrap the +decoded response body in an `Error`. The original body should remain available +under `error.cause.body`; `FormatError` is the right place to unwrap and render +that body. TUI aggregation helpers should call `FormatError` first, then fall +back to generic `Error.message` / string rendering. + +When several parallel startup requests fail from the same underlying issue, +group identical rendered messages and list the affected request names once. +For example: + +```text +Configuration is invalid at /path/to/opencode.json +↳ Expected object, got "not-object" provider.bad.options +Affected startup requests: config.providers, provider.list, app.agents, config.get +``` ## Middleware Guidance @@ -99,6 +171,15 @@ middleware should shrink. It should not gain new name checks. Unknown `500` responses should log full details server-side with `Cause.pretty(cause)` and return a safe public body. +The config startup regression in #27056 is the failure mode this rule is meant +to avoid: a user-authored invalid `opencode.json` crossed the HttpApi boundary +as a defect, so middleware replaced a useful `ConfigInvalidError` with a safe +generic `UnknownError`. The compatibility fix is to preserve config parse and +validation errors as client-visible `400`s. The target architecture is better: +config loading should fail on the typed error channel, config HTTP handlers +should map those errors to declared `ConfigApiError` responses, and the generic +middleware should never see them. + ## Migration Order Prefer small vertical slices: @@ -113,6 +194,9 @@ Prefer small vertical slices: Good early domains are storage not-found, worktree errors, and provider auth validation errors because they currently drive HTTP behavior. +Config parse and validation errors are also a good early slice because they +are startup-blocking and must be rendered clearly in both CLI and TUI flows. + ## Checklist For A PR - [ ] Expected failures are typed errors, not defects. diff --git a/packages/opencode/specs/effect/facades.md b/packages/opencode/specs/effect/facades.md index 8bf7d97bad..f7e3165f00 100644 --- a/packages/opencode/specs/effect/facades.md +++ b/packages/opencode/specs/effect/facades.md @@ -6,7 +6,6 @@ Current status on this branch: - `src/` has 5 `makeRuntime(...)` call sites total. - 2 are intentionally excluded from this checklist: `src/bus/index.ts` and `src/effect/cross-spawn-spawner.ts`. -- 1 is tracked primarily by the instance-context migration rather than facade removal: `src/project/instance.ts`. - That leaves 2 live runtime-backed service facades still worth tracking here: `src/npm/index.ts` and `src/cli/cmd/tui/config/tui.ts`. Recent progress: @@ -18,7 +17,6 @@ Recent progress: - `src/cli/cmd/tui/config/tui.ts` still exports `makeRuntime(...)` plus async facade helpers for `get()` and `waitForDependencies()`. - `src/npm/index.ts` still exports `makeRuntime(...)` plus async facade helpers for `install()`, `add()`, `outdated()`, and `which()`. -- `src/project/instance.ts` still uses a dedicated runtime for project boot, but that file is really part of the broader legacy instance-context transition tracked in `instance-context.md`. ## Completed Batches @@ -192,7 +190,6 @@ Most of the original facade-removal backlog is already done. The practical remai 1. remove the `Npm` runtime-backed facade from `src/npm/index.ts` 2. remove the `TuiConfig` runtime-backed facade from `src/cli/cmd/tui/config/tui.ts` -3. keep `src/project/instance.ts` in the separate instance-context migration, not this checklist ## Checklist diff --git a/packages/opencode/specs/effect/guide.md b/packages/opencode/specs/effect/guide.md index 5df0293448..e8a1a19c56 100644 --- a/packages/opencode/specs/effect/guide.md +++ b/packages/opencode/specs/effect/guide.md @@ -197,13 +197,9 @@ For background loops, use `Effect.repeat` or `Effect.schedule` with [`EffectBridge`](../../src/effect/bridge.ts) is the sanctioned helper for Promise/callback interop that needs to preserve instance/workspace context. -Keep it, but reduce its dependency on legacy `Instance.current` / -`Instance.restore` over time. - -`Instance.bind` / `Instance.restore` are transitional legacy tools. Use -them only for native callbacks that still require legacy ALS context. Do -not use them for `setTimeout`, `Promise.then`, `EventEmitter.on`, or -Effect fibers. +It preserves explicit `InstanceRef` / `WorkspaceRef` context for effects run +through the bridge. Plain JS callbacks that need instance data should receive +that data explicitly. ## Testing diff --git a/packages/opencode/specs/effect/instance-context.md b/packages/opencode/specs/effect/instance-context.md index 6d63715030..94564004c9 100644 --- a/packages/opencode/specs/effect/instance-context.md +++ b/packages/opencode/specs/effect/instance-context.md @@ -1,309 +1,13 @@ -# Instance context migration +# Instance Context -Practical plan for retiring the promise-backed / ALS-backed `Instance` helper in `src/project/instance.ts` and moving instance selection fully into Effect-provided scope. +Instance selection is now Effect-provided context. -## Goal +Use these APIs: -End state: +- `InstanceRef` for the current project context. +- `WorkspaceRef` for the current workspace id. +- `InstanceState.context` / `InstanceState.directory` inside Effect services that require an instance. +- `InstanceStore` at entry boundaries that need to load, reload, or dispose project contexts. +- `EffectBridge` for native, plugin, or plain JavaScript callback boundaries that need to re-enter Effect with captured refs. -- request, CLI, TUI, and tool entrypoints shift into an instance through Effect, not `Instance.provide(...)` -- Effect code reads the current instance from `InstanceRef` or its eventual replacement, not from ALS-backed sync getters -- per-directory boot, caching, and disposal are scoped Effect resources, not a module-level `Map>` -- ALS remains only as a temporary bridge for native callback APIs that fire outside the Effect fiber tree - -## Current split - -Today `src/project/instance.ts` still owns two separate concerns: - -- ambient current-instance context through `LocalContext` / `AsyncLocalStorage` -- per-directory boot and deduplication through `cache: Map>` - -At the same time, the Effect side already exists: - -- `src/effect/instance-ref.ts` provides `InstanceRef` and `WorkspaceRef` -- `src/effect/run-service.ts` already attaches those refs when a runtime starts inside an active instance ALS context -- `src/effect/instance-state.ts` already prefers `InstanceRef` and only falls back to ALS when needed - -That means the migration is not "invent instance context in Effect". The migration is "stop relying on the legacy helper as the primary source of truth". - -## End state shape - -Near-term target shape: - -```ts -InstanceScope.with({ directory, workspaceID }, effect) -``` - -Responsibilities of `InstanceScope.with(...)`: - -- resolve `directory`, `project`, and `worktree` -- acquire or reuse the scoped per-directory instance environment -- provide `InstanceRef` and `WorkspaceRef` -- run the caller's Effect inside that environment - -Code inside the boundary should then do one of these: - -```ts -const ctx = yield * InstanceState.context -const dir = yield * InstanceState.directory -``` - -Long-term, once `InstanceState` itself is replaced by keyed layers / `LayerMap`, those reads can move to an `InstanceContext` service without changing the outer migration order. - -## Migration phases - -### Phase 1: stop expanding the legacy surface - -Rules for all new code: - -- do not add new `Instance.directory`, `Instance.worktree`, `Instance.project`, or `Instance.current` reads inside Effect code -- do not add new `Instance.provide(...)` boundaries unless there is no Effect-native seam yet -- use `InstanceState.context`, `InstanceState.directory`, or an explicit `ctx` parameter inside Effect code - -Success condition: - -- the file inventory below only shrinks from here - -### Phase 2: remove direct sync getter reads from Effect services - -Convert Effect services first, before replacing the top-level boundary. These modules already run inside Effect and mostly need `yield* InstanceState.context` or a yielded `ctx` instead of ambient sync access. - -Primary batch, highest payoff: - -- `src/file/index.ts` -- `src/lsp/server.ts` -- `src/worktree/index.ts` -- `src/file/watcher.ts` -- `src/format/formatter.ts` -- `src/session/index.ts` -- `src/project/vcs.ts` - -Mechanical replacement rule: - -- `Instance.directory` -> `ctx.directory` or `yield* InstanceState.directory` -- `Instance.worktree` -> `ctx.worktree` -- `Instance.project` -> `ctx.project` - -Do not thread strings manually through every public method if the service already has access to Effect context. - -### Phase 3: convert entry boundaries to provide instance refs directly - -After the service bodies stop assuming ALS, move the top-level boundaries to shift into Effect explicitly. - -Main boundaries: - -- HTTP server middleware and experimental `HttpApi` entrypoints -- CLI commands -- TUI worker / attach / thread entrypoints -- tool execution entrypoints - -These boundaries should become Effect-native wrappers that: - -- decode directory / workspace inputs -- resolve the instance context once -- provide `InstanceRef` and `WorkspaceRef` -- run the requested Effect - -At that point `Instance.provide(...)` becomes a legacy adapter instead of the normal code path. - -### Phase 4: replace promise boot cache with scoped instance runtime - -Once boundaries and services both rely on Effect context, replace the module-level promise cache in `src/project/instance.ts`. - -Target replacement: - -- keyed scoped runtime or keyed layer acquisition for each directory -- reuse via `ScopedCache`, `LayerMap`, or another keyed Effect resource manager -- cleanup performed by scope finalizers instead of `disposeAll()` iterating a Promise map - -This phase should absorb the current responsibilities of: - -- `cache` in `src/project/instance.ts` -- `boot(...)` -- most of `disposeInstance(...)` -- manual `reload(...)` / `disposeAll()` fan-out logic - -### Phase 5: shrink ALS to callback bridges only - -Keep ALS only where a library invokes callbacks outside the Effect fiber tree and we still need to call code that reads instance context synchronously. - -Known bridge cases today: - -- `src/file/watcher.ts` -- `src/session/llm.ts` -- some LSP and plugin callback paths - -If those libraries become fully wrapped in Effect services, the remaining `Instance.bind(...)` uses can disappear too. - -### Phase 6: delete the legacy sync API - -Only after earlier phases land: - -- remove broad use of `Instance.current`, `Instance.directory`, `Instance.worktree`, `Instance.project` -- reduce `src/project/instance.ts` to a thin compatibility shim or delete it entirely -- remove the ALS fallback from `InstanceState.context` - -## Inventory of direct legacy usage - -Direct legacy usage means any source file that still calls one of: - -- `Instance.current` -- `Instance.directory` -- `Instance.worktree` -- `Instance.project` -- `Instance.provide(...)` -- `Instance.bind(...)` -- `Instance.restore(...)` -- `Instance.reload(...)` -- `Instance.dispose()` / `Instance.disposeAll()` - -Current total: `56` files in `packages/opencode/src`. - -### Core bridge and plumbing - -These files define or adapt the current bridge. They should change last, after callers have moved. - -- `src/project/instance.ts` -- `src/effect/run-service.ts` -- `src/effect/instance-state.ts` -- `src/project/bootstrap.ts` -- `src/config/config.ts` - -Migration rule: - -- keep these as compatibility glue until the outer boundaries and inner services stop depending on ALS - -### HTTP and server boundaries - -These are the current request-entry seams that still create or consume instance context through the legacy helper. - -- `src/server/routes/instance/middleware.ts` -- `src/server/routes/instance/index.ts` -- `src/server/routes/instance/project.ts` -- `src/server/routes/control/workspace.ts` -- `src/server/routes/instance/file.ts` -- `src/server/routes/instance/experimental.ts` -- `src/server/routes/global.ts` - -Migration rule: - -- move these to explicit Effect entrypoints that provide `InstanceRef` / `WorkspaceRef` -- do not move these first; first reduce the number of downstream handlers and services that still expect ambient ALS - -### CLI and TUI boundaries - -These commands still enter an instance through `Instance.provide(...)` or read sync getters directly. - -- `src/cli/bootstrap.ts` -- `src/cli/cmd/agent.ts` -- `src/cli/cmd/debug/agent.ts` -- `src/cli/cmd/debug/ripgrep.ts` -- `src/cli/cmd/github.ts` -- `src/cli/cmd/import.ts` -- `src/cli/cmd/mcp.ts` -- `src/cli/cmd/models.ts` -- `src/cli/cmd/plug.ts` -- `src/cli/cmd/pr.ts` -- `src/cli/cmd/providers.ts` -- `src/cli/cmd/stats.ts` -- `src/cli/cmd/tui/attach.ts` -- `src/cli/cmd/tui/plugin/runtime.ts` -- `src/cli/cmd/tui/thread.ts` -- `src/cli/cmd/tui/worker.ts` - -Migration rule: - -- converge these on one shared `withInstance(...)` Effect entry helper instead of open-coded `Instance.provide(...)` -- after that helper is proven, inline the legacy implementation behind an Effect-native scope provider - -### Tool boundary code - -These tools mostly use direct getters for path resolution and repo-relative display logic. - -- `src/tool/apply_patch.ts` -- `src/tool/bash.ts` -- `src/tool/edit.ts` -- `src/tool/lsp.ts` -- `src/tool/plan.ts` -- `src/tool/read.ts` -- `src/tool/write.ts` - -Migration rule: - -- expose the current instance as an explicit Effect dependency for tool execution -- keep path logic local; avoid introducing another global singleton for tool state - -### Effect services still reading ambient instance state - -These modules are already the best near-term migration targets because they are in Effect code but still read sync getters from the legacy helper. - -- `src/agent/agent.ts` -- `src/cli/cmd/tui/config/tui-migrate.ts` -- `src/file/index.ts` -- `src/file/watcher.ts` -- `src/format/formatter.ts` -- `src/lsp/client.ts` -- `src/lsp/index.ts` -- `src/lsp/server.ts` -- `src/mcp/index.ts` -- `src/project/vcs.ts` -- `src/provider/provider.ts` -- `src/pty/index.ts` -- `src/session/session.ts` -- `src/session/instruction.ts` -- `src/session/llm.ts` -- `src/session/system.ts` -- `src/sync/index.ts` -- `src/worktree/index.ts` - -Migration rule: - -- replace direct getter reads with `yield* InstanceState.context` or a yielded `ctx` -- isolate `Instance.bind(...)` callers and convert only the truly callback-driven edges to bridge mode - -### Highest-churn hotspots - -Current highest direct-usage counts by file: - -- `src/file/index.ts` - `18` -- `src/lsp/server.ts` - `14` -- `src/worktree/index.ts` - `12` -- `src/file/watcher.ts` - `9` -- `src/cli/cmd/mcp.ts` - `8` -- `src/format/formatter.ts` - `8` -- `src/tool/apply_patch.ts` - `8` -- `src/cli/cmd/github.ts` - `7` - -These files should drive the first measurable burn-down. - -## Recommended implementation order - -1. Migrate direct getter reads inside Effect services, starting with `file`, `lsp`, `worktree`, `format`, and `session`. -2. Add one shared Effect-native boundary helper for CLI / tool / HTTP entrypoints so we stop open-coding `Instance.provide(...)`. -3. Move experimental `HttpApi` entrypoints to that helper so the new server stack proves the pattern. -4. Convert remaining CLI and tool boundaries. -5. Replace the promise cache with a keyed scoped runtime or keyed layer map. -6. Delete ALS fallback paths once only callback bridges still depend on them. - -## Definition of done - -This migration is done when all of the following are true: - -- new requests and commands enter an instance by providing Effect context, not ALS -- Effect services no longer read `Instance.directory`, `Instance.worktree`, `Instance.project`, or `Instance.current` -- `Instance.provide(...)` is gone from normal request / CLI / tool execution -- per-directory boot and disposal are handled by scoped Effect resources -- `Instance.bind(...)` is either gone or confined to a tiny set of native callback adapters - -## Tracker and worktree - -Active tracker items: - -- `lh7l73` - overall `HttpApi` migration -- `yobwlk` - remove direct `Instance.*` reads inside Effect services -- `7irl1e` - replace `InstanceState` / legacy instance caching with keyed Effect layers - -Dedicated worktree for this transition: - -- path: `/Users/kit/code/open-source/opencode-worktrees/instance-effect-shift` -- branch: `kit/instance-effect-shift` +Do not add new ambient instance globals. Promise and callback boundaries should either stay in Effect, use `EffectBridge`, or pass the required context explicitly. diff --git a/packages/opencode/specs/effect/loose-ends.md b/packages/opencode/specs/effect/loose-ends.md index 4e7ada7ff9..d30efd1815 100644 --- a/packages/opencode/specs/effect/loose-ends.md +++ b/packages/opencode/specs/effect/loose-ends.md @@ -24,10 +24,6 @@ Small follow-ups that do not fit neatly into the main facade, route, tool, or sc - [ ] `cli/cmd/tui/config/tui.ts` - switch TUI config loading from async `ConfigPaths.*` wrappers to the `ConfigPaths.Service` once that service exists. - [ ] `cli/cmd/tui/config/tui-migrate.ts` - decide whether to leave this as a plain async module using wrapper functions or effectify it fully after `ConfigPaths.Service` lands. -## Instance cleanup - -- [ ] `project/instance.ts` - keep shrinking the legacy ALS / Promise cache after the remaining `Instance.*` callers move over. - ## Notes - Prefer small, semantics-preserving config migrations. Config precedence, legacy key migration, and plugin origin tracking are easy to break accidentally. diff --git a/packages/opencode/specs/effect/todo.md b/packages/opencode/specs/effect/todo.md index 68bcd0d717..67fa49f806 100644 --- a/packages/opencode/specs/effect/todo.md +++ b/packages/opencode/specs/effect/todo.md @@ -64,13 +64,11 @@ P6 OA explicit and testable instead of mutable module state. Shrinks: [`global.ts`](../../../core/src/global.ts) import-time side effects, mutable `Global.Path` overrides, and its `Flag` dependency. -- `INST` Instance shim — remove ambient `Instance` usage and old ALS - access patterns. - Shrinks: [`src/project/instance.ts`](../../src/project/instance.ts). +- `INST` Instance context — keep project context explicit through Effect refs + and bridge boundaries. - `BRIDGE` Promise/callback interop — keep bridge helpers, but reduce legacy ALS coupling. - Shrinks: [`src/effect/bridge.ts`](../../src/effect/bridge.ts) - dependency on [`project/instance.ts`](../../src/project/instance.ts). + Shrinks: ad hoc Promise/callback re-entry code. - `PROC` AppProcess migration — prefer `AppProcess.Service` over raw process wrappers. Shrinks: direct spawn callsites and legacy process helpers. @@ -221,74 +219,13 @@ Next PR candidates: ## P4: Instance And Bridge -[`project/instance.ts`](../../src/project/instance.ts) is the deletion -target. [`effect/bridge.ts`](../../src/effect/bridge.ts) is not a near-term -deletion target; Promise/callback interop will continue to exist. - -Goal: - -- Keep a sanctioned bridge for Promise/callback boundaries. -- Reduce bridge dependence on legacy `Instance.restore` / `Instance.current`. -- Move callers toward `InstanceRef`, `WorkspaceRef`, `InstanceState`, or - explicit context where practical. -- Delete `project/instance.ts` only after ambient Instance coupling is gone. - -Important distinction: - -- `InstanceState.context`, `InstanceState.directory`, and - `InstanceState.workspaceID` are acceptable inside normal Effect service - code when `InstanceRef` / `WorkspaceRef` are provided by the runtime. -- The deletion blockers are the fallback and callback paths that rely on - ambient ALS: direct `Instance.*` reads, `InstanceState.bind(...)`, - `AppRuntime.runPromise(...)` re-entry from plain JS, and bridge restore - code that installs legacy ALS before invoking callbacks. - -Current bottom-up inventory from `dev`: - -- Direct `Instance.*` value readers: - [`tool/repo_overview.ts`](../../src/tool/repo_overview.ts), - [`control-plane/adapters/worktree.ts`](../../src/control-plane/adapters/worktree.ts), - [`cli/bootstrap.ts`](../../src/cli/bootstrap.ts). -- `InstanceState.bind(...)` callback boundaries: - [`file/watcher.ts`](../../src/file/watcher.ts) native watcher callback, - [`storage/db.ts`](../../src/storage/db.ts) transaction/effect callbacks, - [`session/llm.ts`](../../src/session/llm.ts) workflow approval callback. -- `AppRuntime.runPromise(...)` / re-entry from plain JS: - [`project/with-instance.ts`](../../src/project/with-instance.ts), - [`project/instance-runtime.ts`](../../src/project/instance-runtime.ts), - [`control-plane/adapters/worktree.ts`](../../src/control-plane/adapters/worktree.ts), - [`cli/effect-cmd.ts`](../../src/cli/effect-cmd.ts), plus global/non-instance - callsites such as CLI upgrade and ACP agent defaults. -- Intentional bridge users to classify, not delete blindly: - workspace adapters in [`control-plane/workspace.ts`](../../src/control-plane/workspace.ts), - MCP, command execution, plugins, pty lifecycle, bus scope cleanup, task - cancellation, and HTTP lifecycle reload/dispose paths. -- Core fallback layer to shrink last: - [`effect/run-service.ts`](../../src/effect/run-service.ts), - [`effect/bridge.ts`](../../src/effect/bridge.ts), and - [`effect/instance-state.ts`](../../src/effect/instance-state.ts). - -Recommended PR order: - -- [ ] `INST-1` Remove direct `Instance.*` value readers. Start with - `repo_overview`, `worktree` adapter, and `cli/bootstrap`; pass context - explicitly or obtain it from an Effect boundary. -- [ ] `INST-2` Move type-only `InstanceContext` imports from - [`project/instance.ts`](../../src/project/instance.ts) to - [`project/instance-context.ts`](../../src/project/instance-context.ts). -- [ ] `INST-3` Audit each `InstanceState.bind(...)` callback from the inside - out: list what the callback calls (`Bus.publish`, database effects, - permission/session services), then replace ambient capture with explicit - `InstanceRef` / `WorkspaceRef` provision or an `EffectBridge` call. -- [ ] `INST-4` Classify `AppRuntime.runPromise(...)` callsites as global, - instance-scoped with explicit refs, or bridge-required. Eliminate the - instance-scoped callsites that rely on `run-service.attach()` falling - back to `Instance.current`. -- [ ] `INST-5` After consumers are explicit, remove `Instance.current` fallback - from `InstanceState.context` and `run-service.attach()`. -- [ ] `INST-6` Move any remaining `restore` / `bind` compatibility helpers to - the boundary that still needs them, then delete - [`project/instance.ts`](../../src/project/instance.ts). +Instance context migration is complete for the legacy sync shim. Promise and callback interop continues through [`effect/bridge.ts`](../../src/effect/bridge.ts). + +Current rules: + +- Effect services read instance data from `InstanceRef`, `WorkspaceRef`, `InstanceState`, or explicit arguments. +- Plain JavaScript callback boundaries use `EffectBridge` or explicit context arguments. +- Runtime entrypoints must provide refs explicitly when they are instance-scoped. ## Lower Priority Tracks diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 683bc7df55..1b804bec15 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -39,10 +39,9 @@ import { Filesystem } from "@/util/filesystem" import { Hash } from "@opencode-ai/core/util/hash" import { ACPSessionManager } from "./session" import type { ACPConfig } from "./types" +import { ACPRuntime } from "./runtime" import { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "../provider/schema" -import { Agent as AgentModule } from "../agent/agent" -import { AppRuntime } from "@/effect/app-runtime" import { Installation } from "@/installation" import { MessageV2 } from "@/session/message-v2" import { Config } from "@/config/config" @@ -545,7 +544,7 @@ export class Agent implements ACPAgent { "terminal-auth": { command: "opencode", args: ["auth", "login"], - label: "Kilo Login", // kilocode_change + label: "Kilo Login", }, } } @@ -571,7 +570,7 @@ export class Agent implements ACPAgent { }, authMethods: [authMethod], agentInfo: { - name: "Kilo", // kilocode_change + name: "Kilo", version: InstallationVersion, }, } @@ -1119,7 +1118,7 @@ export class Agent implements ACPAgent { const currentModeId = await (async () => { if (!availableModes.length) return undefined - const defaultAgent = await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultInfo())) + const defaultAgent = await ACPRuntime.defaultAgentInfo(directory) const resolvedModeId = availableModes.find((mode) => mode.name === defaultAgent.name)?.id ?? availableModes[0].id this.sessionManager.setMode(sessionId, resolvedModeId) return resolvedModeId @@ -1353,8 +1352,7 @@ export class Agent implements ACPAgent { if (!current) { this.sessionManager.setModel(session.id, model) } - const agent = - session.modeId ?? (await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultInfo()))).name + const agent = session.modeId ?? (await ACPRuntime.defaultAgentInfo(directory)).name const parts: Array< | { type: "text"; text: string; synthetic?: boolean; ignored?: boolean } diff --git a/packages/opencode/src/acp/runtime.ts b/packages/opencode/src/acp/runtime.ts new file mode 100644 index 0000000000..b08c73cf2e --- /dev/null +++ b/packages/opencode/src/acp/runtime.ts @@ -0,0 +1,22 @@ +import { Agent } from "@/agent/agent" +import { AppRuntime, type AppServices } from "@/effect/app-runtime" +import { InstanceRef } from "@/effect/instance-ref" +import { InstanceRuntime } from "@/project/instance-runtime" +import { Effect } from "effect" + +// Global ACP Effect re-entry: no project InstanceRef is provided. +export const runGlobal = AppRuntime.runPromise + +// Directory-scoped ACP Effect re-entry: load the project instance and provide InstanceRef. +export async function runDirectory(input: { directory: string; effect: Effect.Effect }) { + const ctx = await InstanceRuntime.load({ directory: input.directory }) + return AppRuntime.runPromise(input.effect.pipe(Effect.provideService(InstanceRef, ctx))) +} + +export const defaultAgentInfo = (directory: string) => + runDirectory({ + directory, + effect: Agent.Service.use((svc) => svc.defaultInfo()), + }) + +export * as ACPRuntime from "./runtime" diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index b50f405d31..943752bbb0 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -93,6 +93,7 @@ export const layer = Layer.effect( Effect.fn("Agent.state")(function* (ctx) { const cfg = yield* config.get() const skillDirs = yield* skill.dirs() + // kilocode_change start - include global config dirs so agents can read them without prompting const whitelistedDirs = [ Truncate.GLOB, path.join(Global.Path.tmp, "*"), @@ -114,7 +115,7 @@ export const layer = Layer.effect( "*": "ask", ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])), }, - suggest: "deny", + suggest: "deny", // kilocode_change question: "deny", plan_enter: "deny", plan_exit: "deny", @@ -129,8 +130,10 @@ export const layer = Layer.effect( }, }) + // kilocode_change start - patch defaults with bash allowlist and recall permission const kilo = KiloAgent.prepare(cfg) const defaults = Permission.merge(baseDefaults, kilo.defaultsPatch) + // kilocode_change end const user = Permission.fromConfig(cfg.permission ?? {}) @@ -143,7 +146,7 @@ export const layer = Layer.effect( defaults, Permission.fromConfig({ question: "allow", - suggest: "allow", + suggest: "allow", // kilocode_change plan_enter: "allow", }), user, @@ -294,6 +297,7 @@ export const layer = Layer.effect( const agentConfigs = KiloAgent.preprocessConfig(cfg.agent ?? {}) for (const [key, value] of Object.entries(agentConfigs)) { + // kilocode_change end if (value.disable) { delete agents[key] continue @@ -320,7 +324,7 @@ export const layer = Layer.effect( item.steps = value.steps ?? item.steps item.options = mergeDeep(item.options, value.options ?? {}) item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {})) - KiloAgent.processConfigItem(item) + KiloAgent.processConfigItem(item) // kilocode_change - populate displayName from options } function referencePrompt(reference: Reference.Resolved) { @@ -410,7 +414,7 @@ export const layer = Layer.effect( } const get = Effect.fnUntraced(function* (agent: string) { - return agents[KiloAgent.resolveKey(agent)] + return agents[KiloAgent.resolveKey(agent)] // kilocode_change - treat "build" as "code" }) const list = Effect.fnUntraced(function* () { @@ -419,7 +423,7 @@ export const layer = Layer.effect( agents, values(), sortBy( - [(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "code"), "desc"], + [(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "code"), "desc"], // kilocode_change - renamed from "build" to "code" [(x) => x.name.startsWith("lh-"), "desc"], // czcode_change - data agents before coding agents // kilocode_change [(x) => x.name, "asc"], ), @@ -438,6 +442,7 @@ export const layer = Layer.effect( if (agent.hidden === true) throw new Error(`default agent "${c.default_agent}" is hidden`) return agent } + // kilocode_change start - prefer "code" as default agent (key order changes after rename from "build") const code = agents.code if (code && code.mode !== "subagent" && code.hidden !== true) return code // kilocode_change end @@ -501,7 +506,9 @@ export const layer = Layer.effect( const isOpenaiOauth = model.providerID === "openai" && authInfo?.type === "oauth" const params = { + // kilocode_change start - enable telemetry with custom PostHog tracer experimental_telemetry: KiloAgent.telemetryOptions(cfg), + // kilocode_change end temperature: 0.3, messages: [ ...(isOpenaiOauth diff --git a/packages/opencode/src/bus/bus-event.ts b/packages/opencode/src/bus/bus-event.ts index 3533706318..5a9e52ef07 100644 --- a/packages/opencode/src/bus/bus-event.ts +++ b/packages/opencode/src/bus/bus-event.ts @@ -1,4 +1,5 @@ import { Schema } from "effect" +import { EventV2 } from "@opencode-ai/core/event" export type Definition = { type: Type @@ -17,16 +18,28 @@ export function define( } export function effectPayloads() { - return registry - .entries() - .map(([type, def]) => - Schema.Struct({ - id: Schema.String, - type: Schema.Literal(type), - properties: def.properties, - }).annotate({ identifier: `Event.${type}` }), - ) - .toArray() + return [ + ...registry + .entries() + .map(([type, def]) => + Schema.Struct({ + id: Schema.String, + type: Schema.Literal(type), + properties: def.properties, + }).annotate({ identifier: `Event.${type}` }), + ) + .toArray(), + ...EventV2.registry + .values() + .map((definition) => + Schema.Struct({ + id: Schema.String, + type: Schema.Literal(definition.type), + properties: definition.data, + }).annotate({ identifier: `Event.${definition.type}` }), + ) + .toArray(), + ] } export * as BusEvent from "./bus-event" diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index 449694a53a..ea3abd4ac1 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -1,4 +1,4 @@ -import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema } from "effect" +import { Effect, Exit, Fiber, Layer, PubSub, Scope, Context, Stream, Schema } from "effect" // kilocode_change import { EffectBridge } from "@/effect/bridge" import * as Log from "@opencode-ai/core/util/log" import { BusEvent } from "./bus-event" @@ -6,6 +6,9 @@ import { GlobalBus } from "./global" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" import { Identifier } from "@/id/id" +import { context as instanceContext, type InstanceContext } from "@/project/instance-context" // kilocode_change +import { InstanceRef } from "@/effect/instance-ref" +import { LocalContext } from "@/util/local-context" // kilocode_change const log = Log.create({ service: "bus" }) @@ -185,19 +188,45 @@ export function createID() { } export async function publish( + ctx: InstanceContext, def: D, properties: BusProperties, options?: { id?: string }, ) { - return runPromise((svc) => svc.publish(def, properties, options)) + return runPromise((svc) => svc.publish(def, properties, options).pipe(Effect.provideService(InstanceRef, ctx))) +} + +// kilocode_change start - legacy callback facade inherits the active instance context +function active() { + const fiber = Fiber.getCurrent() + const current = fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined + if (current) return current + try { + return instanceContext.use() + } catch (err) { + if (!(err instanceof LocalContext.NotFound)) throw err + } } export function subscribe(def: D, callback: (event: Payload) => unknown) { - return runSync((svc) => svc.subscribeCallback(def, callback)) + const ctx = active() + if (!ctx) throw new Error("Instance context not available") + return runSync((svc) => + svc + .subscribeCallback(def, (event) => instanceContext.provide(ctx, () => callback(event))) + .pipe(Effect.provideService(InstanceRef, ctx)), + ) } export function subscribeAll(callback: (event: any) => unknown) { - return runSync((svc) => svc.subscribeAllCallback(callback)) + const ctx = active() + if (!ctx) throw new Error("Instance context not available") + return runSync((svc) => + svc + .subscribeAllCallback((event) => instanceContext.provide(ctx, () => callback(event))) + .pipe(Effect.provideService(InstanceRef, ctx)), + ) } +// kilocode_change end export * as Bus from "." diff --git a/packages/opencode/src/cli/bootstrap.ts b/packages/opencode/src/cli/bootstrap.ts index fa39ecb177..2308c29199 100644 --- a/packages/opencode/src/cli/bootstrap.ts +++ b/packages/opencode/src/cli/bootstrap.ts @@ -1,17 +1,11 @@ -import { Instance } from "../project/instance" import { InstanceRuntime } from "../project/instance-runtime" -import { WithInstance } from "../project/with-instance" +import { context } from "../project/instance-context" export async function bootstrap(directory: string, cb: () => Promise) { - return WithInstance.provide({ - directory, - fn: async () => { - try { - const result = await cb() - return result - } finally { - await InstanceRuntime.disposeInstance(Instance.current) - } - }, - }) + const ctx = await InstanceRuntime.load({ directory }) + try { + return await context.provide(ctx, cb) + } finally { + await InstanceRuntime.disposeInstance(ctx) + } } diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index c7151a2125..d2c72c2c4d 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -66,6 +66,8 @@ const AgentCreateCommand = effectCmd({ if (!maybeCtx) return yield* Effect.die("InstanceRef not provided") const ctx = maybeCtx const agentSvc = yield* Agent.Service + const runLocalEffect = (effect: Effect.Effect) => + Effect.runPromise(effect.pipe(Effect.provideService(InstanceRef, ctx))) yield* Effect.promise(async () => { const cliPath = args.path const cliDescription = args.description @@ -130,7 +132,7 @@ const AgentCreateCommand = effectCmd({ const spinner = prompts.spinner() spinner.start("Generating agent configuration...") const model = args.model ? Provider.parseModel(args.model) : undefined - const generated = await Effect.runPromise(agentSvc.generate({ description, model })).catch((error) => { + const generated = await runLocalEffect(agentSvc.generate({ description, model })).catch((error) => { spinner.stop(`LLM failed to generate agent: ${error.message}`, 1) if (isFullyNonInteractive) process.exit(1) throw new UI.CancelledError() diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts index 1a3f79396c..ac9879ff89 100644 --- a/packages/opencode/src/cli/cmd/debug/agent.ts +++ b/packages/opencode/src/cli/cmd/debug/agent.ts @@ -11,7 +11,7 @@ import { Permission } from "../../../permission" import { iife } from "../../../util/iife" import { effectCmd, fail } from "../../effect-cmd" import { InstanceRef } from "@/effect/instance-ref" -import type { InstanceContext } from "@/project/instance" +import type { InstanceContext } from "@/project/instance-context" export const AgentCommand = effectCmd({ command: "agent ", diff --git a/packages/opencode/src/cli/cmd/debug/v2.ts b/packages/opencode/src/cli/cmd/debug/v2.ts index 507f82cdf5..836f581367 100644 --- a/packages/opencode/src/cli/cmd/debug/v2.ts +++ b/packages/opencode/src/cli/cmd/debug/v2.ts @@ -1,11 +1,11 @@ import { EOL } from "os" import { Effect, Layer, Option } from "effect" import { Catalog } from "@opencode-ai/core/catalog" -import { InstanceServiceMap } from "@opencode-ai/core/instance-layer" +import { LocationServiceMap } from "@opencode-ai/core/location-layer" import { PluginBoot } from "@opencode-ai/core/plugin/boot" import { effectCmd } from "../../effect-cmd" -const Runtime = Layer.mergeAll(InstanceServiceMap.layer) +const Runtime = Layer.mergeAll(LocationServiceMap.layer) export const V2Command = effectCmd({ command: "v2", @@ -37,7 +37,7 @@ export const V2Command = effectCmd({ process.stdout.write(JSON.stringify(result, null, 2) + EOL) }, Effect.provide( - InstanceServiceMap.get({ + LocationServiceMap.get({ directory: process.cwd(), }), ), diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 7a92f0bd36..3255d74c44 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -34,6 +34,7 @@ import { setTimeout as sleep } from "node:timers/promises" import { Process } from "@/util/process" import { parseGitHubRemote } from "@/util/repository" import { Effect } from "effect" +import { GitHubSecurity } from "@/kilocode/security/github" // kilocode_change type GitHubAuthor = { login: string @@ -442,6 +443,9 @@ export const GithubRunCommand = effectCmd({ const sessionSvc = yield* Session.Service const sessionShare = yield* SessionShare.Service const sessionPrompt = yield* SessionPrompt.Service + const busSvc = yield* Bus.Service + const runLocalEffect = (effect: Effect.Effect) => + Effect.runPromise(effect.pipe(Effect.provideService(InstanceRef, ctx))) yield* Effect.promise(async () => { const isMock = args.token || args.event @@ -555,7 +559,7 @@ export const GithubRunCommand = effectCmd({ // Setup kilo session const repoData = await fetchRepo() - session = await Effect.runPromise( + session = await runLocalEffect( sessionSvc.create({ permission: [ { @@ -566,11 +570,11 @@ export const GithubRunCommand = effectCmd({ ], }), ) - subscribeSessionEvents() + await subscribeSessionEvents() shareId = await (async () => { if (share === false) return if (!share && repoData.data.private) return - await Effect.runPromise(sessionShare.share(session.id)) + await runLocalEffect(sessionShare.share(session.id)) return session.id.slice(-8) })() console.log("kilo session", session.id) @@ -842,7 +846,10 @@ export const GithubRunCommand = effectCmd({ let offset = 0 for (const m of matches) { const tag = m[0] - const url = m[1] + // kilocode_change start - only fetch canonical GitHub attachment routes + const url = GitHubSecurity.attachment(m[1]) + if (!url) continue + // kilocode_change end const start = m.index const filename = path.basename(url) @@ -877,7 +884,7 @@ export const GithubRunCommand = effectCmd({ return { userPrompt: prompt, promptFiles: imgData } } - function subscribeSessionEvents() { + async function subscribeSessionEvents() { const TOOL: Record = { todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD], bash: ["Shell", UI.Style.TEXT_DANGER_BOLD], @@ -900,33 +907,35 @@ export const GithubRunCommand = effectCmd({ } let text = "" - Bus.subscribe(MessageV2.Event.PartUpdated, (evt) => { - if (evt.properties.part.sessionID !== session.id) return - //if (evt.properties.part.messageID === messageID) return - const part = evt.properties.part - - if (part.type === "tool" && part.state.status === "completed") { - const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD] - const title = - part.state.title || Object.keys(part.state.input).length > 0 - ? JSON.stringify(part.state.input) - : "Unknown" - console.log() - printEvent(color, tool, title) - } + await runLocalEffect( + busSvc.subscribeCallback(MessageV2.Event.PartUpdated, (evt) => { + if (evt.properties.part.sessionID !== session.id) return + //if (evt.properties.part.messageID === messageID) return + const part = evt.properties.part + + if (part.type === "tool" && part.state.status === "completed") { + const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD] + const title = + part.state.title || Object.keys(part.state.input).length > 0 + ? JSON.stringify(part.state.input) + : "Unknown" + console.log() + printEvent(color, tool, title) + } - if (part.type === "text") { - text = part.text + if (part.type === "text") { + text = part.text - if (part.time?.end) { - UI.empty() - UI.println(UI.markdown(text)) - UI.empty() - text = "" - return + if (part.time?.end) { + UI.empty() + UI.println(UI.markdown(text)) + UI.empty() + text = "" + return + } } - } - }) + }), + ) } async function summarize(response: string) { @@ -943,7 +952,7 @@ export const GithubRunCommand = effectCmd({ async function chat(message: string, files: PromptFiles = []) { console.log("Sending message to kilo...") - return Effect.runPromise( + return runLocalEffect( Effect.gen(function* () { const prompt = sessionPrompt const result = yield* prompt.prompt({ diff --git a/packages/opencode/src/cli/cmd/prompt-display.ts b/packages/opencode/src/cli/cmd/prompt-display.ts index 7ec4bc0af5..4e8cb9046a 100644 --- a/packages/opencode/src/cli/cmd/prompt-display.ts +++ b/packages/opencode/src/cli/cmd/prompt-display.ts @@ -1,11 +1,20 @@ const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" }) +function promptOffsetWidth(value: string) { + let width = 0 + for (const part of graphemes.segment(value)) { + // Textarea offsets count newlines as one position; Bun.stringWidth counts them as zero. + width += part.segment === "\n" ? 1 : Bun.stringWidth(part.segment) + } + return width +} + function displayOffsetIndex(value: string, offset: number) { if (offset <= 0) return 0 let width = 0 for (const part of graphemes.segment(value)) { - const next = width + Bun.stringWidth(part.segment) + const next = width + promptOffsetWidth(part.segment) if (next > offset) return part.index width = next } @@ -13,20 +22,20 @@ function displayOffsetIndex(value: string, offset: number) { return value.length } -export function displaySlice(value: string, start = 0, end = Bun.stringWidth(value)) { +export function displaySlice(value: string, start = 0, end = promptOffsetWidth(value)) { return value.slice(displayOffsetIndex(value, start), displayOffsetIndex(value, end)) } export function displayCharAt(value: string, offset: number) { let width = 0 for (const part of graphemes.segment(value)) { - const next = width + Bun.stringWidth(part.segment) + const next = width + promptOffsetWidth(part.segment) if (offset === width || offset < next) return part.segment width = next } } -export function mentionTriggerIndex(value: string, offset = Bun.stringWidth(value)) { +export function mentionTriggerIndex(value: string, offset = promptOffsetWidth(value)) { const text = displaySlice(value, 0, offset) const index = text.lastIndexOf("@") if (index === -1) return @@ -34,6 +43,6 @@ export function mentionTriggerIndex(value: string, offset = Bun.stringWidth(valu const before = index === 0 ? undefined : text[index - 1] const query = text.slice(index) if ((before === undefined || /\s/.test(before)) && !/\s/.test(query)) { - return Bun.stringWidth(text.slice(0, index)) + return promptOffsetWidth(text.slice(0, index)) } } diff --git a/packages/opencode/src/cli/cmd/remote.ts b/packages/opencode/src/cli/cmd/remote.ts index 95459186d7..51b222e0a7 100644 --- a/packages/opencode/src/cli/cmd/remote.ts +++ b/packages/opencode/src/cli/cmd/remote.ts @@ -2,7 +2,7 @@ import { cmd } from "./cmd" import { bootstrap } from "../bootstrap" import { KiloSessions } from "@/kilo-sessions/kilo-sessions" -import { Instance } from "@/project/instance" +import { context } from "@/project/instance-context" import { InstanceRuntime } from "@/project/instance-runtime" export const RemoteCommand = cmd({ @@ -18,7 +18,7 @@ export const RemoteCommand = cmd({ const shutdown = async () => { try { KiloSessions.disableRemote() - await InstanceRuntime.disposeInstance(Instance.current) + await InstanceRuntime.disposeInstance(context.use()) } finally { abort.abort() } diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index eb744e7362..cd2e23f7dd 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -27,6 +27,7 @@ import { createKiloClient, type KiloClient, type ToolPart } from "@kilocode/sdk/ import { Agent } from "@/agent/agent" import { Permission } from "@/permission" import { RuntimeFlags } from "@/effect/runtime-flags" +import { InstanceRef } from "@/effect/instance-ref" import { FormatError, FormatUnknownError } from "../error" import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./run/runtime.stdin" import { event as normalizeEvent } from "./run/event" @@ -202,7 +203,7 @@ export const RunCommand = effectCmd({ }) .option("attach", { type: "string", - describe: "attach to a running kilo server (e.g., http://localhost:4096)", // kilocode_change + describe: "attach to a running kilo server (e.g., http://localhost:4096)", }) .option("password", { alias: ["p"], @@ -256,6 +257,7 @@ export const RunCommand = effectCmd({ handler: Effect.fn("Cli.run")(function* (args) { const agentSvc = yield* Agent.Service const flags = yield* RuntimeFlags.Service + const localInstance = yield* InstanceRef yield* Effect.promise(async () => { const rawMessage = [...args.message, ...(args["--"] || [])].join(" ") const thinking = args.interactive ? (args.thinking ?? true) : (args.thinking ?? false) @@ -566,7 +568,9 @@ export const RunCommand = effectCmd({ if (!args.agent) return undefined const name = args.agent - const entry = await Effect.runPromise(agentSvc.get(name)) + const entry = await Effect.runPromise( + agentSvc.get(name).pipe(Effect.provideService(InstanceRef, localInstance)), + ) if (!entry) { UI.println( UI.Style.TEXT_WARNING_BOLD + "!", diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index c5c745bba7..30718a860e 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -1,9 +1,9 @@ -import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" // kilocode_change +import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui" import * as Clipboard from "@tui/util/clipboard" import * as Selection from "@tui/util/selection" import * as TuiAudio from "@tui/util/audio" -import { createCliRenderer, MouseButton, TextAttributes, type CliRendererConfig } from "@opentui/core" // kilocode_change +import { createCliRenderer, MouseButton, TextAttributes, type CliRendererConfig } from "@opentui/core" import { RouteProvider, useRoute } from "@tui/context/route" import { Switch, @@ -18,12 +18,12 @@ import { Show, on, } from "solid-js" -import { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "./win32" // kilocode_change +import { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "./win32" import { Flag } from "@opencode-ai/core/flag/flag" import semver from "semver" import { DialogProvider, useDialog } from "@tui/ui/dialog" import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider" -import { InstallationVersion } from "@opencode-ai/core/installation/version" // kilocode_change +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { PluginRouteMissing } from "@tui/component/plugin-route-missing" import { ProjectProvider } from "@tui/context/project" import { EditorContextProvider } from "@tui/context/editor" @@ -53,10 +53,8 @@ import { DialogConfirm } from "./ui/dialog-confirm" import { ToastProvider, useToast } from "./ui/toast" import { ExitProvider, useExit } from "./context/exit" import { Session as SessionApi } from "@/session/session" -// kilocode_change start import { DialogSelect } from "./ui/dialog-select" import { Link } from "./ui/link" -// kilocode_change end import { TuiEvent } from "./event" import { KVProvider, useKV } from "./context/kv" import { Provider } from "@/provider/provider" @@ -71,8 +69,8 @@ import { createTuiApi } from "@/cli/cmd/tui/plugin/api" import type { RouteMap } from "@/cli/cmd/tui/plugin/api" import { createTuiAttention } from "@/cli/cmd/tui/attention" import { FormatError, FormatUnknownError } from "@/cli/error" -import { kitty, resetTerminalState } from "@/kilocode/cli/cmd/tui/util/terminal" // kilocode_change -import * as AppExit from "@/kilocode/tui/app-exit" // kilocode_change +import { kitty, resetTerminalState } from "@/kilocode/cli/cmd/tui/util/terminal" +import * as AppExit from "@/kilocode/tui/app-exit" import { CommandPaletteProvider, useCommandPalette } from "./context/command-palette" import { OpencodeKeymapProvider, registerOpencodeKeymap, useBindings, useOpencodeKeymap } from "./keymap" @@ -83,8 +81,6 @@ const appBindingCommands = [ "command.palette.show", "session.list", "session.new", - "session.cycle_recent", - "session.cycle_recent_reverse", "session.quick_switch.1", "session.quick_switch.2", "session.quick_switch.3", @@ -127,14 +123,14 @@ const appBindingCommands = [ function rendererConfig(_config: TuiConfig.Resolved): CliRendererConfig { const mouseEnabled = !Flag.KILO_DISABLE_MOUSE && (_config.mouse ?? true) - const keyboard = kitty() // kilocode_change + const keyboard = kitty() return { externalOutputMode: "passthrough", targetFps: 60, gatherStats: false, exitOnCtrlC: false, - ...(keyboard ? { useKittyKeyboard: {} } : {}), // kilocode_change + ...(keyboard ? { useKittyKeyboard: {} } : {}), autoFocus: false, openConsoleOnError: false, useMouse: mouseEnabled, @@ -345,7 +341,7 @@ function App(props: { onSnapshot?: () => Promise }) { if (!text || text.length === 0) return await Clipboard.copy(text) - .then(() => toast.show({ message: "Copied to clipboard", variant: "info", duration: 2000 })) // czcode_change - auto-dismiss // kilocode_change + .then(() => toast.show({ message: "Copied to clipboard", variant: "info", duration: 2000 })) // czcode_change - auto-dismiss .catch(toast.error) renderer.clearSelection() @@ -355,10 +351,8 @@ function App(props: { onSnapshot?: () => Promise }) { kv.get("paste_summary_enabled", !sync.data.config.experimental?.disable_paste_summary), ) - // kilocode_change start KiloApp.useSessionEffects({ route, sdk, sync }) KiloApp.useTuiConfigHotReload() - // kilocode_change end // Update terminal window title based on current route and session createEffect(() => { @@ -367,24 +361,24 @@ function App(props: { onSnapshot?: () => Promise }) { const titleDefault = KiloApp.APP_TITLE if (route.data.type === "home") { - renderer.setTerminalTitle(titleDefault) // kilocode_change + renderer.setTerminalTitle(titleDefault) return } if (route.data.type === "session") { const session = sync.session.get(route.data.sessionID) if (!session || SessionApi.isDefaultTitle(session.title)) { - renderer.setTerminalTitle(titleDefault) // kilocode_change + renderer.setTerminalTitle(titleDefault) return } const title = session.title.length > 40 ? session.title.slice(0, 37) + "..." : session.title - renderer.setTerminalTitle(`${titleDefault} | ${title}`) // kilocode_change + renderer.setTerminalTitle(`${titleDefault} | ${title}`) return } if (route.data.type === "plugin") { - renderer.setTerminalTitle(`${titleDefault} | ${route.data.id}`) // kilocode_change + renderer.setTerminalTitle(`${titleDefault} | ${route.data.id}`) } const kiloTitle = KiloApp.getTerminalTitle(route, titleDefault) @@ -466,7 +460,7 @@ function App(props: { onSnapshot?: () => Promise }) { const connected = useConnected() const appCommands = createMemo(() => - [ + [ { name: "command.palette.show", title: "Show command palette", @@ -501,37 +495,15 @@ function App(props: { onSnapshot?: () => Promise }) { dialog.clear() }, }, - ...(Flag.KILO_EXPERIMENTAL_SESSION_SWITCHING - ? [ - { - name: "session.cycle_recent", - title: "Cycle to previous recent session", - category: "Session", - hidden: true, - run: () => { - local.session.cycleRecent(1) - }, - }, - { - name: "session.cycle_recent_reverse", - title: "Cycle to next recent session", - category: "Session", - hidden: true, - run: () => { - local.session.cycleRecent(-1) - }, - }, - ...Array.from({ length: 9 }, (_, i) => ({ - name: `session.quick_switch.${i + 1}`, - title: `Switch to session in quick slot ${i + 1}`, - category: "Session", - hidden: true, - run: () => { - local.session.quickSwitch(i + 1) - }, - })), - ] - : []), + ...Array.from({ length: 9 }, (_, i) => ({ + name: `session.quick_switch.${i + 1}`, + title: `Switch to session in quick slot ${i + 1}`, + category: "Session", + hidden: true, + run: () => { + local.session.quickSwitch(i + 1) + }, + })), { name: "model.list", title: "Switch model", @@ -712,7 +684,7 @@ function App(props: { onSnapshot?: () => Promise }) { }, category: "System", }, - AppExit.command(exit), // kilocode_change + AppExit.command(exit), { name: "app.debug", title: "Toggle debug panel", @@ -839,18 +811,11 @@ function App(props: { onSnapshot?: () => Promise }) { useBindings(() => ({ enabled: command.matcher, - bindings: tuiConfig.keybinds.gather( - "app", - Flag.KILO_EXPERIMENTAL_SESSION_SWITCHING - ? appBindingCommands - : appBindingCommands.filter( - (c) => !c.startsWith("session.cycle_recent") && !c.startsWith("session.quick_switch"), - ), - ), + bindings: tuiConfig.keybinds.gather("app", appBindingCommands), })) useBindings(() => ({ - enabled: () => AppExit.enabled(command.matcher.get(), promptRef.current), // kilocode_change + enabled: () => AppExit.enabled(command.matcher.get(), promptRef.current), bindings: tuiConfig.keybinds.gather("app_exit", ["app.exit"]), })) @@ -986,13 +951,11 @@ function App(props: { onSnapshot?: () => Promise }) { - {/* kilocode_change start */} {/* czcode_change start */} {/* czcode_change end */} - {/* kilocode_change end */} {plugin()} diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 12e7b2e929..1be189658c 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -14,7 +14,7 @@ import { createDebouncedSignal } from "../util/signal" import { useToast } from "../ui/toast" import { openWorkspaceSelect, type WorkspaceSelection, warpWorkspaceSession } from "./dialog-workspace-create" import { Spinner } from "./spinner" -import path from "path" +import path from "path" // kilocode_change import { errorMessage } from "@/util/error" import { DialogSessionDeleteFailed } from "./dialog-session-delete-failed" import { WorkspaceLabel } from "./workspace-label" @@ -33,6 +33,8 @@ export function DialogSessionList() { const [search, setSearch] = createDebouncedSignal("", 150) const [global, setGlobal] = createSignal(true) // kilocode_change - show all worktrees by default const deleteHint = useCommandShortcut("session.delete") + const quickSwitch1 = useCommandShortcut("session.quick_switch.1") + const quickSwitch9 = useCommandShortcut("session.quick_switch.9") // kilocode_change start - always fetch from experimental endpoint (returns GlobalSession with worktree info) // TODO: extend /experimental/session to accept `scope`/`path` so this dialog can respect the @@ -53,9 +55,11 @@ export function DialogSessionList() { return result.data ?? [] }, ) + // kilocode_change end const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined)) + // kilocode_change start - client-side worktree filtering when global is off const sessions = createMemo(() => { const all = searchResults() ?? [] if (global()) return all @@ -63,6 +67,7 @@ export function DialogSessionList() { if (!root || root === "/") return all return all.filter((s) => s.directory === root || s.directory.startsWith(root + path.sep)) }) + // kilocode_change end function recover(session: NonNullable[number]>) { const workspace = project.workspace.get(session.workspaceID!) @@ -118,7 +123,7 @@ export function DialogSessionList() { } await project.workspace.sync() await sync.session.refresh() - if (search()) await searchActions.refetch() + if (search()) await searchActions.refetch() // kilocode_change - use createResource actions if (info?.workspaceID === session.workspaceID) { route.navigate({ type: "home" }) } @@ -151,10 +156,18 @@ export function DialogSessionList() { const [browseOrder] = createSignal(orderByRecency(sync.data.session)) - const RECENT_LIMIT = 5 + const quickSwitchHint = createMemo(() => { + const first = quickSwitch1() + const last = quickSwitch9() + if (!first || !last) return undefined + return quickSwitchRange(first, last) + }) + const quickSwitchFooterHints = createMemo(() => { + const hint = quickSwitchHint() + return hint && local.session.slots().length > 0 ? [{ title: "switch", label: hint }] : [] + }) const options = createMemo(() => { - const enabled = Flag.KILO_EXPERIMENTAL_SESSION_SWITCHING const today = new Date().toDateString() const all = global() // kilocode_change const sessionMap = new Map( @@ -166,17 +179,9 @@ export function DialogSessionList() { const searchResult = searchResults() const displayOrder = searchResult ? orderByRecency(searchResult) : browseOrder() - const dismissed = enabled ? new Set(local.session.dismissedRecent()) : new Set() - const pinned = enabled ? local.session.pinned().filter((id) => sessionMap.has(id)) : [] + const pinned = local.session.pinned().filter((id) => sessionMap.has(id)) const pinnedSet = new Set(pinned) - const slotByID = enabled - ? new Map(local.session.slots().map((id, i) => [id, i + 1])) - : new Map() - - const recent = enabled - ? displayOrder.filter((id) => !pinnedSet.has(id) && !dismissed.has(id)).slice(0, RECENT_LIMIT) - : [] - const recentSet = new Set(recent) + const slotByID = new Map(local.session.slots().map((id, i) => [id, i + 1])) function buildOption(id: string, category: string) { const x = sessionMap.get(id) @@ -221,7 +226,7 @@ export function DialogSessionList() { } const remaining = displayOrder - .filter((id) => !pinnedSet.has(id) && !recentSet.has(id)) + .filter((id) => !pinnedSet.has(id)) .map((id) => { const x = sessionMap.get(id) if (!x) return undefined @@ -230,11 +235,7 @@ export function DialogSessionList() { }) .filter((x) => x !== undefined) - return [ - ...pinned.map((id) => buildOption(id, "Pinned")).filter((x) => x !== undefined), - ...recent.map((id) => buildOption(id, "Recent")).filter((x) => x !== undefined), - ...remaining, - ] + return [...pinned.map((id) => buildOption(id, "Pinned")).filter((x) => x !== undefined), ...remaining] }) onMount(() => { @@ -243,7 +244,7 @@ export function DialogSessionList() { return ( { - local.session.togglePin(option.value) - }, - }, - { - command: "session.toggle.recent", - title: "toggle recent", - onTrigger: (option: { value: string }) => { - if (local.session.isPinned(option.value)) { - toast.show({ - variant: "info", - message: "Unpin the session first to toggle it in Recent", - duration: 3000, - }) - return - } - local.session.toggleRecent(option.value) - }, - }, - ] - : []), + { + command: "session.pin.toggle", + title: "pin/unpin", + onTrigger: (option: { value: string }) => { + local.session.togglePin(option.value) + }, + }, { command: "session.delete", title: "delete", @@ -326,7 +308,7 @@ export function DialogSessionList() { if (status && status !== "connected") { await sync.session.refresh() } - void searchActions.refetch() + void searchActions.refetch() // kilocode_change setToDelete(undefined) return } @@ -335,7 +317,7 @@ export function DialogSessionList() { }, { command: "session.rename", - title: "rename", // kilocode_change + title: "rename", // kilocode_change start onTrigger: async (option) => { const item = sessions().find((x) => x.id === option.value) @@ -363,6 +345,13 @@ export function DialogSessionList() { // kilocode_change start - preserve Ctrl+A worktree scope toggle with the upstream keymap engine bindings={[{ key: "ctrl+a", cmd: "session.scope.toggle" }]} // kilocode_change end + footerHints={quickSwitchFooterHints()} /> ) } + +function quickSwitchRange(first: string, last: string) { + const prefix = first.slice(0, -1) + if (first.endsWith("1") && last === `${prefix}9`) return `${prefix}1-9` + return `${first} through ${last}` +} diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx index 8b1f2ca366..48dcc54370 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx @@ -27,6 +27,11 @@ export type PromptInfo = { const MAX_HISTORY_ENTRIES = 50 +export function isDuplicateEntry(previous: PromptInfo | undefined, next: PromptInfo): boolean { + if (!previous) return false + return JSON.stringify(previous) === JSON.stringify(next) +} + export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({ name: "PromptHistory", init: () => { @@ -83,6 +88,10 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create }, append(item: PromptInfo) { const entry = structuredClone(unwrap(item)) + if (isDuplicateEntry(store.history.at(-1), entry)) { + setStore("index", 0) + return + } let trimmed = false setStore( produce((draft) => { diff --git a/packages/opencode/src/cli/cmd/tui/config/keybind.ts b/packages/opencode/src/cli/cmd/tui/config/keybind.ts index f6d05dd311..5c7bd2507e 100644 --- a/packages/opencode/src/cli/cmd/tui/config/keybind.ts +++ b/packages/opencode/src/cli/cmd/tui/config/keybind.ts @@ -87,9 +87,6 @@ export const Definitions = { session_child_cycle_reverse: keybind("left", "Go to previous child session"), session_parent: keybind("up", "Go to parent session"), session_pin_toggle: keybind("ctrl+f", "Pin or unpin session in the session list"), - session_toggle_recent: keybind("ctrl+h", "Show or hide session in the Recent group"), - session_cycle_recent: keybind("]", "Cycle to the previous recent session"), - session_cycle_recent_reverse: keybind("[", "Cycle to the next recent session"), session_quick_switch_1: keybind("1", "Switch to session in quick slot 1"), session_quick_switch_2: keybind("2", "Switch to session in quick slot 2"), session_quick_switch_3: keybind("3", "Switch to session in quick slot 3"), @@ -278,9 +275,6 @@ export const CommandMap = { session_child_cycle_reverse: "session.child.previous", session_parent: "session.parent", session_pin_toggle: "session.pin.toggle", - session_toggle_recent: "session.toggle.recent", - session_cycle_recent: "session.cycle_recent", - session_cycle_recent_reverse: "session.cycle_recent_reverse", session_quick_switch_1: "session.quick_switch.1", session_quick_switch_2: "session.quick_switch.2", session_quick_switch_3: "session.quick_switch.3", diff --git a/packages/opencode/src/cli/cmd/tui/context/aggregate-failures.ts b/packages/opencode/src/cli/cmd/tui/context/aggregate-failures.ts index 63b3fb4487..8b652b6512 100644 --- a/packages/opencode/src/cli/cmd/tui/context/aggregate-failures.ts +++ b/packages/opencode/src/cli/cmd/tui/context/aggregate-failures.ts @@ -1,3 +1,5 @@ +import { FormatError } from "@/cli/error" + /** * Aggregate Promise.allSettled results into a single Error that names every * failed endpoint, or return null when all fulfilled. Used at TUI bootstrap @@ -15,7 +17,19 @@ export function aggregateFailures(labeled: LabeledSettled[]): Error | null { ) if (failed.length === 0) return null - const reasons = failed.map((f) => `${f.name}: ${reasonMessage(f.result.reason)}`).join("; ") + const reasons = Array.from( + failed + .map((f) => ({ name: f.name, message: reasonMessage(f.result.reason) })) + .reduce((grouped, failure) => { + grouped.set(failure.message, [...(grouped.get(failure.message) ?? []), failure.name]) + return grouped + }, new Map()) + .entries(), + ) + .map(([message, names]) => + names.length === 1 ? `${names[0]}: ${message}` : `${message}\nAffected startup requests: ${names.join(", ")}`, + ) + .join("; ") const summary = `${failed.length} of ${labeled.length} requests failed: ${reasons}` const err = new Error(summary) err.cause = { failures: failed.map((f) => ({ name: f.name, reason: f.result.reason })) } @@ -23,6 +37,9 @@ export function aggregateFailures(labeled: LabeledSettled[]): Error | null { } function reasonMessage(reason: unknown): string { + const formatted = FormatError(reason) + if (formatted) return formatted + if (reason instanceof Error) return reason.message if (typeof reason === "string") return reason if (reason && typeof reason === "object") { diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index 1c162fab79..cc86e39cc2 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -1,6 +1,6 @@ import { createStore } from "solid-js/store" import { createSimpleContext } from "./helper" -import { batch, createEffect, createMemo, on } from "solid-js" +import { batch, createEffect, createMemo } from "solid-js" import { useSync } from "@tui/context/sync" import { useTheme } from "@tui/context/theme" import { useRoute } from "@tui/context/route" @@ -8,7 +8,6 @@ import { useEvent } from "@tui/context/event" import { uniqueBy } from "remeda" import path from "path" import { Global } from "@opencode-ai/core/global" -import { Flag } from "@opencode-ai/core/flag/flag" import { iife } from "@/util/iife" import { useToast } from "../ui/toast" import { useArgs } from "./args" @@ -454,13 +453,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const [sessionStore, setSessionStore] = createStore<{ ready: boolean pinned: string[] - dismissedRecent: string[] - recentOrder: string[] }>({ ready: false, pinned: [], - dismissedRecent: [], - recentOrder: [], }) const filePath = path.join(Global.Path.state, "session.json") @@ -476,16 +471,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ state.pending = false void Filesystem.writeJson(filePath, { pinned: sessionStore.pinned, - dismissedRecent: sessionStore.dismissedRecent, - recentOrder: sessionStore.recentOrder, }) } Filesystem.readJson(filePath) .then((x: any) => { if (Array.isArray(x.pinned)) setSessionStore("pinned", x.pinned) - if (Array.isArray(x.dismissedRecent)) setSessionStore("dismissedRecent", x.dismissedRecent) - if (Array.isArray(x.recentOrder)) setSessionStore("recentOrder", x.recentOrder) }) .catch(() => {}) .finally(() => { @@ -495,19 +486,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const route = useRoute() const event = useEvent() - let cycling = false const slots = createMemo(() => { - const rootSessions = sync.data.session.filter((x) => x.parentID === undefined) - const existing = new Set(rootSessions.map((x) => x.id)) - const dismissed = new Set(sessionStore.dismissedRecent) - const pins = sessionStore.pinned.filter((id) => existing.has(id)) - const pinnedSet = new Set(pins) - const recent = rootSessions - .filter((x) => !pinnedSet.has(x.id) && !dismissed.has(x.id)) - .toSorted((a, b) => b.time.updated - a.time.updated) - .map((x) => x.id) - return [...pins, ...recent].slice(0, 9) + const existing = new Set(sync.data.session.filter((x) => x.parentID === undefined).map((x) => x.id)) + return sessionStore.pinned.filter((id) => existing.has(id)).slice(0, 9) }) function prune(sessionID: string) { @@ -518,18 +500,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ sessionStore.pinned.filter((x) => x !== sessionID), ) } - if (sessionStore.dismissedRecent.includes(sessionID)) { - setSessionStore( - "dismissedRecent", - sessionStore.dismissedRecent.filter((x) => x !== sessionID), - ) - } - if (sessionStore.recentOrder.includes(sessionID)) { - setSessionStore( - "recentOrder", - sessionStore.recentOrder.filter((x) => x !== sessionID), - ) - } save() }) } @@ -538,25 +508,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ prune(evt.data.sessionID) }) - if (Flag.KILO_EXPERIMENTAL_SESSION_SWITCHING) { - createEffect( - on( - () => (sessionStore.ready && route.data.type === "session" ? route.data.sessionID : undefined), - (sessionID) => { - if (!sessionID) return - if (cycling) { - cycling = false - return - } - const filtered = sessionStore.recentOrder.filter((x) => x !== sessionID) - const next = [sessionID, ...filtered].slice(0, 20) - setSessionStore("recentOrder", next) - save() - }, - ), - ) - } - return { get ready() { return sessionStore.ready @@ -564,75 +515,26 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ pinned() { return sessionStore.pinned }, - dismissedRecent() { - return sessionStore.dismissedRecent - }, - recentOrder() { - return sessionStore.recentOrder - }, slots, isPinned(sessionID: string) { return sessionStore.pinned.includes(sessionID) }, - isDismissed(sessionID: string) { - return sessionStore.dismissedRecent.includes(sessionID) - }, togglePin(sessionID: string) { batch(() => { const exists = sessionStore.pinned.includes(sessionID) const next = exists ? sessionStore.pinned.filter((x) => x !== sessionID) - : [sessionID, ...sessionStore.pinned] + : [...sessionStore.pinned, sessionID] setSessionStore("pinned", next) save() }) }, - toggleRecent(sessionID: string) { - batch(() => { - const exists = sessionStore.dismissedRecent.includes(sessionID) - const next = exists - ? sessionStore.dismissedRecent.filter((x) => x !== sessionID) - : [sessionID, ...sessionStore.dismissedRecent] - setSessionStore("dismissedRecent", next) - save() - }) - }, quickSwitch(slot: number) { const target = slots()[slot - 1] if (!target) return if (route.data.type === "session" && route.data.sessionID === target) return route.navigate({ type: "session", sessionID: target }) }, - cycleRecent(direction: 1 | -1) { - if (route.data.type !== "session") { - toast.show({ - variant: "info", - message: "Open a session first to cycle between recent sessions", - duration: 3000, - }) - return - } - const current = route.data.sessionID - const order = sessionStore.recentOrder.filter((id) => - sync.data.session.some((s) => s.id === id && s.parentID === undefined), - ) - if (order.length < 2) { - toast.show({ - variant: "info", - message: "No other recent sessions to cycle to", - duration: 3000, - }) - return - } - const index = order.indexOf(current) - if (index === -1) return - const next = index + direction - if (next < 0 || next >= order.length) return - const target = order[next] - if (!target || target === current) return - cycling = true - route.navigate({ type: "session", sessionID: target }) - }, } }) diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 1e3cb55f11..5e6a6fe1ee 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -9,8 +9,8 @@ import type { Command, PermissionRequest, QuestionRequest, - SuggestionRequest, - SessionNetworkWait, + SuggestionRequest, // kilocode_change + SessionNetworkWait, // kilocode_change LspStatus, McpStatus, McpResource, @@ -58,12 +58,14 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ question: { [sessionID: string]: QuestionRequest[] } + // kilocode_change start suggestion: { [sessionID: string]: SuggestionRequest[] } network: { [sessionID: string]: SessionNetworkWait[] } + // kilocode_change end config: Config globalConfig: Config // kilocode_change session: Session[] @@ -71,7 +73,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ [sessionID: string]: SessionStatus } session_diff: { - [sessionID: string]: Omit[] + [sessionID: string]: Omit[] // kilocode_change } todo: { [sessionID: string]: Todo[] @@ -96,7 +98,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ } formatter: FormatterStatus[] vcs: VcsInfo | undefined - indexing: IndexingStatus + indexing: IndexingStatus // kilocode_change }>({ provider_next: { all: [], @@ -112,8 +114,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ agent: [], permission: {}, question: {}, + // kilocode_change start suggestion: {}, network: {}, + // kilocode_change end command: [], provider: [], provider_default: {}, @@ -129,7 +133,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ mcp_resource: {}, formatter: [], vcs: undefined, - indexing: { state: "Disabled", message: "Indexing disabled.", processedFiles: 0, totalFiles: 0, percent: 0 }, + indexing: { state: "Disabled", message: "Indexing disabled.", processedFiles: 0, totalFiles: 0, percent: 0 }, // kilocode_change }) const event = useEvent() @@ -138,6 +142,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ const toast = useToast() // kilocode_change const kv = useKV() + // kilocode_change start function evict(sessionID: string) { // Collect child session IDs so we can evict them too. const children = store.session.filter((s) => s.parentID === sessionID).map((s) => s.id) @@ -168,6 +173,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ if (msg.role !== "user" || !msg.summary?.diffs) return msg return { ...msg, summary: { ...msg.summary, diffs: [] } } as Message } + // kilocode_change end const fullSyncedSessions = new Set() const deleted = new Set() // kilocode_change @@ -272,8 +278,9 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }), ) break - } + } // kilocode_change + // kilocode_change start case "session.network.replied": case "session.network.rejected": { const requests = store.network[event.properties.sessionID] @@ -329,6 +336,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ ) break } + // kilocode_change end case "todo.updated": setStore("todo", event.properties.sessionID, event.properties.todos) break @@ -417,6 +425,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ break } + // kilocode_change start case "global.config.updated": { sdk.client.global.config.get().then((x) => { if (x.data) setStore("globalConfig", reconcile(x.data)) @@ -430,6 +439,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ setStore("indexing", reconcile(event.properties.status)) break } + // kilocode_change end } }) @@ -710,6 +720,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ } }), project.workspace.sync(), + // kilocode_change start - show config warnings as persistent toast sdk.client.config .warnings() .then((x) => { @@ -728,6 +739,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ sdk.client.indexing .status({ workspace }) .then((x) => setStore("indexing", reconcile(x.data ?? store.indexing))), + // kilocode_change end ]).then(() => { setStore("status", "complete") }) @@ -750,6 +762,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ void bootstrap() }) + // kilocode_change start - re-bootstrap when workspace changes (Agent Manager) createEffect( on( () => project.workspace.current(), @@ -760,6 +773,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ { defer: true }, ), ) + // kilocode_change end const result = { data: store, @@ -768,7 +782,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ return store.status }, get ready() { - // return true + // return true // kilocode_change - upstream #23037 left this debug path enabled; keep it commented so future merges do not restore eager ready state. if (process.env.KILO_FAST_BOOT) return true return store.status !== "loading" }, @@ -823,7 +837,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ ) fullSyncedSessions.add(sessionID) }, - evict, + evict, // kilocode_change }, bootstrap, } diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx index 6c0fee733f..28bbbcf2f3 100644 --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx @@ -984,6 +984,7 @@ function getSyntaxRules(theme: Theme) { style: { foreground: theme.markdownHeading, bold: true, + underline: true, }, }, { diff --git a/packages/opencode/src/cli/cmd/tui/context/thinking.ts b/packages/opencode/src/cli/cmd/tui/context/thinking.ts new file mode 100644 index 0000000000..55e995df11 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/context/thinking.ts @@ -0,0 +1,66 @@ +import { createMemo, type Setter } from "solid-js" +import { useKV } from "./kv" + +export type ThinkingMode = "show" | "hide" + +const MODES: readonly ThinkingMode[] = ["show", "hide"] as const + +// OpenAI's Responses API surfaces reasoning summaries that start with a bolded +// title line: "**Inspecting PR workflow**\n\n". GitHub Copilot routes +// through the same shape, and the opencode provider relays it too. Pull the +// title out for a nicer label; return null for providers that don't follow +// this convention so the caller can fall back to a generic "Thinking" string. +export function reasoningTitle(text: string): string | null { + const match = text.trimStart().match(/^\*\*([^*\n]+)\*\*/) + return match ? match[1].trim() : null +} + +export function isThinkingMode(value: unknown): value is ThinkingMode { + return typeof value === "string" && (MODES as readonly string[]).includes(value) +} + +// Cycle order matches the slash command: show → hide → show. +export function nextThinkingMode(current: ThinkingMode): ThinkingMode { + const idx = MODES.indexOf(current) + return MODES[(idx + 1) % MODES.length] ?? "show" +} + +export function useThinkingMode() { + const kv = useKV() + // Capture pre-state before `kv.signal` seeds a default, so we can detect + // first-time users with a legacy `thinking_visibility` boolean and migrate. + // The KVProvider only renders children once kv.ready, so reads here are safe. + const hadStored = kv.get("thinking_mode") !== undefined + const legacy = kv.get("thinking_visibility") + const [stored, setStored] = kv.signal("thinking_mode", "hide") + + // The kv signal exposes its setter typed as `Setter` which carries Solid's + // overload set; passing an updater fn through a property access loses the + // bivariance trick the existing `setX((prev) => ...)` callsites rely on. + // Wrap it in a sane shape so consumers can just call `set(next)` or pass + // an updater. + const set = (next: ThinkingMode | ((prev: ThinkingMode) => ThinkingMode)) => { + if (typeof next === "function") setStored(next as Setter) + else setStored(() => next) + } + + // Preserve previous experience for users who had explicitly toggled the + // legacy `thinking_visibility` boolean. First-time users (no legacy key) + // get the new "hide" default (collapsed thinking). + if (!hadStored) { + if (legacy === true) set("show") + else if (legacy === false) set("hide") + } + + if ((stored() as string) === "minimal") set("hide") + + const mode = createMemo(() => { + const value = stored() + return isThinkingMode(value) ? value : "hide" + }) + + return { + mode, + set, + } +} diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx index 15489f2aab..3988fb2ff1 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx @@ -1,7 +1,6 @@ import type { TuiPluginApi } from "@kilocode/plugin/tui" import { createMemo, For, type Accessor } from "solid-js" import { DEFAULT_THEMES, useTheme } from "@tui/context/theme" -import { Flag } from "@opencode-ai/core/flag/flag" import { useCommandShortcut } from "../../keymap" const themeCount = Object.keys(DEFAULT_THEMES).length @@ -29,8 +28,6 @@ type Shortcuts = { messagesToggleConceal: TipShortcut modelCycleRecent: TipShortcut modelList: TipShortcut - sessionCycleRecent: TipShortcut - sessionCycleRecentReverse: TipShortcut sessionExport: TipShortcut sessionInterrupt: TipShortcut sessionList: TipShortcut @@ -41,7 +38,6 @@ type Shortcuts = { sessionQuickSwitch9: TipShortcut sessionSidebarToggle: TipShortcut sessionTimeline: TipShortcut - sessionToggleRecent: TipShortcut statusView: TipShortcut terminalSuspend: TipShortcut themeList: TipShortcut @@ -73,6 +69,7 @@ function parse(tip: string): TipPart[] { } const NO_MODELS_TIP = "Run {highlight}/connect{/highlight} to add an AI provider and start coding" +const NO_MODELS_PARTS = parse(NO_MODELS_TIP) function shortcutText(value: string) { return `{highlight}${value}{/highlight}` @@ -121,8 +118,6 @@ export function Tips(props: { api: TuiPluginApi; connected?: boolean }) { messagesToggleConceal: configShortcut(props.api, "session.toggle.conceal"), modelCycleRecent: useCommandShortcut("model.cycle_recent"), modelList: useCommandShortcut("model.list"), - sessionCycleRecent: useCommandShortcut("session.cycle_recent"), - sessionCycleRecentReverse: useCommandShortcut("session.cycle_recent_reverse"), sessionExport: configShortcut(props.api, "session.export"), sessionInterrupt: configShortcut(props.api, "session.interrupt"), sessionList: useCommandShortcut("session.list"), @@ -133,7 +128,6 @@ export function Tips(props: { api: TuiPluginApi; connected?: boolean }) { sessionQuickSwitch9: useCommandShortcut("session.quick_switch.9"), sessionSidebarToggle: configShortcut(props.api, "session.sidebar.toggle"), sessionTimeline: configShortcut(props.api, "session.timeline"), - sessionToggleRecent: configShortcut(props.api, "session.toggle.recent"), statusView: useCommandShortcut("opencode.status"), terminalSuspend: useCommandShortcut("terminal.suspend"), themeList: useCommandShortcut("theme.switch"), @@ -145,8 +139,13 @@ export function Tips(props: { api: TuiPluginApi; connected?: boolean }) { return value ? [value] : [] }) return tips[Math.floor(tipOffset * tips.length)] ?? NO_MODELS_TIP - }) - const parts = createMemo(() => parse(tip())) + }, NO_MODELS_TIP) + // Solid can expose a memo's initial value while a pure computation is pending. + const parts = createMemo(() => { + const value = tip() + if (typeof value === "string") return parse(value) + return NO_MODELS_PARTS + }, NO_MODELS_PARTS) return ( @@ -176,23 +175,12 @@ const TIPS: Tip[] = [ (shortcuts) => `Use ${commandText("/models", shortcuts.modelList())} to see and switch between available AI models`, (shortcuts) => `Use ${commandText("/themes", shortcuts.themeList())} to switch between ${themeCount} built-in themes`, (shortcuts) => `Use ${commandText("/new", shortcuts.sessionNew())} to start a fresh conversation session`, - (shortcuts) => `Use ${commandText("/sessions", shortcuts.sessionList())} to list and continue previous conversations`, - ...(Flag.KILO_EXPERIMENTAL_SESSION_SWITCHING - ? ([ - (shortcuts) => - press(shortcuts.sessionPinToggle(), "in the session list to pin a session so it stays at the top"), - (shortcuts) => - shortcuts.sessionQuickSwitch1() && shortcuts.sessionQuickSwitch9() - ? `Pinned and recent sessions are bound to ${shortcutText(shortcuts.sessionQuickSwitch1())} through ${shortcutText(shortcuts.sessionQuickSwitch9())} for one-press switching` - : undefined, - (shortcuts) => - shortcuts.sessionCycleRecent() && shortcuts.sessionCycleRecentReverse() - ? `Press ${shortcutText(shortcuts.sessionCycleRecent())} / ${shortcutText(shortcuts.sessionCycleRecentReverse())} to cycle through recently visited sessions` - : undefined, - (shortcuts) => - press(shortcuts.sessionToggleRecent(), "in the session list to show or hide a session in the Recent group"), - ] satisfies Tip[]) - : []), + (shortcuts) => `Use ${commandText("/sessions", shortcuts.sessionList())} to list, pin, and continue sessions`, + (shortcuts) => press(shortcuts.sessionPinToggle(), "in the session list to pin a session so it stays at the top"), + (shortcuts) => + shortcuts.sessionQuickSwitch1() && shortcuts.sessionQuickSwitch9() + ? `Pinned sessions are assigned quick slots; use ${shortcutText(shortcuts.sessionQuickSwitch1())} through ${shortcutText(shortcuts.sessionQuickSwitch9())} to switch` + : undefined, "Run {highlight}/compact{/highlight} to summarize long sessions near context limits", (shortcuts) => `Use ${commandText("/export", shortcuts.sessionExport())} to save the conversation as Markdown`, (shortcuts) => press(shortcuts.messagesCopy(), "to copy the assistant's last message to clipboard"), @@ -245,15 +233,15 @@ const TIPS: Tip[] = [ "Tool definitions can invoke scripts written in Python, Go, etc", "Add {highlight}.ts{/highlight} files to {highlight}.opencode/plugins/{/highlight} for event hooks", "Use plugins to send OS notifications when sessions complete", - "Create a plugin to prevent Kilo from reading sensitive files", // kilocode_change + "Create a plugin to prevent Kilo from reading sensitive files", "Use {highlight}kilo run{/highlight} for non-interactive scripting", // kilocode_change "Use {highlight}kilo --continue{/highlight} to resume the last session", // kilocode_change "Use {highlight}kilo run -f file.ts{/highlight} to attach files via CLI", // kilocode_change "Use {highlight}--format json{/highlight} for machine-readable output in scripts", - "Run {highlight}kilo serve{/highlight} for headless API access to Kilo", // kilocode_change + "Run {highlight}kilo serve{/highlight} for headless API access to Kilo", "Use {highlight}kilo run --attach{/highlight} to connect to a running server", // kilocode_change - "Run {highlight}kilo upgrade{/highlight} to update to the latest version", // kilocode_change - "Run {highlight}kilo auth list{/highlight} to see all configured providers", // kilocode_change + "Run {highlight}kilo upgrade{/highlight} to update to the latest version", + "Run {highlight}kilo auth list{/highlight} to see all configured providers", "Run {highlight}kilo agent create{/highlight} for guided agent creation", // kilocode_change "Use {highlight}/opencode{/highlight} in GitHub issues/PRs to trigger AI actions", "Run {highlight}kilo github install{/highlight} to set up the GitHub workflow", // kilocode_change diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx index f3d29d9016..130bfa71e3 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx @@ -5,6 +5,7 @@ import { SplitBorder } from "@tui/component/border" import { Spinner } from "@tui/component/spinner" import { useTheme } from "@tui/context/theme" import { useLocal } from "@tui/context/local" +import { reasoningTitle, useThinkingMode } from "@tui/context/thinking" import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid" import { TextAttributes, type BoxRenderable, type SyntaxStyle } from "@opentui/core" import { useBindings } from "../../keymap" @@ -317,7 +318,11 @@ function AssistantMessage(props: { - + props.message.time.completed} + /> @@ -378,30 +383,64 @@ function AssistantText(props: { part: SessionMessageAssistantText; syntax: Synta ) } -function AssistantReasoning(props: { part: SessionMessageAssistantReasoning; subtleSyntax: SyntaxStyle }) { +function AssistantReasoning(props: { + part: SessionMessageAssistantReasoning + subtleSyntax: SyntaxStyle + completedAt: () => number | undefined +}) { const { theme } = useTheme() + const thinking = useThinkingMode() + const [expanded, setExpanded] = createSignal(false) const content = createMemo(() => props.part.text.replace("[REDACTED]", "").trim()) + const inMinimal = createMemo(() => thinking.mode() === "hide") + // v2 reasoning parts have no per-part `time.end` (see SessionMessageAssistantReasoning + // in the v2 SDK); we settle on parent-message completion instead. + const isDone = createMemo(() => props.completedAt() !== undefined) + const title = createMemo(() => reasoningTitle(content())) + + const toggle = () => { + if (!inMinimal()) return + setExpanded((prev) => !prev) + } + return ( - - - + + + + + + + + + + {title() ? "▶ Thought: " + title() : "▶ Thought"} + + + + + + {title() ? "Thinking: " + title() : "Thinking"} + + + ) } diff --git a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts index 1e400b3b80..6acdc7676c 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts @@ -1,6 +1,5 @@ import HomeFooter from "../feature-plugins/home/footer" import HomeTips from "../feature-plugins/home/tips" -// kilocode_change start import HomeNews from "@/kilocode/plugins/home-news" import HomeOnboarding from "@/kilocode/plugins/home-onboarding" import KiloAttention from "@/kilocode/plugins/attention" @@ -10,7 +9,6 @@ import KiloSidebarBackgroundProcesses from "@/kilocode/plugins/sidebar-backgroun import KiloSidebarIndexing from "@/kilocode/plugins/sidebar-indexing" import KiloSidebarPr from "@/kilocode/plugins/sidebar-pr" import KiloSidebarUsage from "@/kilocode/plugins/sidebar-usage" -// kilocode_change end // czcode_change start import CzCodeConnectionStatus from "@/kilocode/plugins/czcode-connection-status" import CzCodeRoleSwitch from "@/kilocode/plugins/czcode-role-switch" @@ -43,15 +41,15 @@ export type InternalTuiPlugin = Omit & { export function internalTuiPlugins(flags: Pick): InternalTuiPlugin[] { return [ - HomeNews, // kilocode_change - HomeOnboarding, // kilocode_change - KiloAttention, // kilocode_change - KiloHomeFooter, // kilocode_change - KiloSidebarFooter, // kilocode_change - KiloSidebarBackgroundProcesses, // kilocode_change - KiloSidebarIndexing, // kilocode_change - KiloSidebarPr, // kilocode_change - KiloSidebarUsage, // kilocode_change + HomeNews, + HomeOnboarding, + KiloAttention, + KiloHomeFooter, + KiloSidebarFooter, + KiloSidebarBackgroundProcesses, + KiloSidebarIndexing, + KiloSidebarPr, + KiloSidebarUsage, CzCodeConnectionStatus, // czcode_change CzCodeRoleSwitch, // czcode_change CzCodeSchemaBrowser, // czcode_change diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 099f723a0f..3b3f3fa739 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -3,7 +3,7 @@ import { createContext, createEffect, createMemo, - onCleanup, // kilocode_change + onCleanup, createSignal, For, Match, @@ -24,14 +24,11 @@ import { SplitBorder } from "@tui/component/border" import { Spinner } from "@tui/component/spinner" import { selectedForeground, useTheme } from "@tui/context/theme" import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core" -// kilocode_change start import type { KeyEvent } from "@opentui/core" import type { CommandContext } from "@opentui/keymap" -// kilocode_change end import { Prompt, type PromptRef } from "@tui/component/prompt" import type { AssistantMessage, Part, Provider, ToolPart, UserMessage, TextPart, ReasoningPart } from "@kilocode/sdk/v2" import * as Log from "@opencode-ai/core/util/log" -// kilocode_change end import { useLocal } from "@tui/context/local" import { Locale } from "@/util/locale" import type { Tool } from "@/tool/tool" @@ -49,10 +46,8 @@ import { webSearchProviderLabel, type WebSearchTool } from "@/tool/websearch" import type { TaskTool } from "@/tool/task" import type { QuestionTool } from "@/tool/question" import type { SkillTool } from "@/tool/skill" -// kilocode_change start import type { BackgroundProcessTool } from "@/kilocode/tool/background-process" import type { SemanticSearchTool } from "@/kilocode/tool/semantic-search" -// kilocode_change end import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid" import { useSDK } from "@tui/context/sdk" import { useEditorContext } from "@tui/context/editor" @@ -65,10 +60,8 @@ import { DialogConfirm } from "@tui/ui/dialog-confirm" import { KiloErrorBlock } from "@/kilocode/components/kilo-error-display" import { DialogTimeline } from "./dialog-timeline" import { DialogForkFromTimeline } from "./dialog-fork-from-timeline" -// kilocode_change start import { Link } from "@tui/ui/link" // czcode_change import { t } from "@/kilocode/plugins/czcode-i18n" // czcode_change -// kilocode_change end import { DialogSessionRename } from "../../component/dialog-session-rename" import { Sidebar } from "./sidebar" import { SubagentFooter } from "./subagent-footer.tsx" @@ -85,23 +78,20 @@ import { useExit } from "../../context/exit" import { Filesystem } from "@/util/filesystem" import { PermissionPrompt } from "./permission" import { QuestionPrompt } from "./question" -// kilocode_change start import { Suggest } from "@/kilocode/suggestion/tui/render" import { SuggestPrompt } from "@/kilocode/suggestion/tui/prompt" import { NetworkPrompt } from "./network" -// kilocode_change end import { DialogExportOptions } from "../../ui/dialog-export-options" import * as Model from "../../util/model" import { formatTranscript } from "../../util/transcript" import { UI } from "@/cli/ui.ts" import { useTuiConfig } from "../../context/tui-config" -// kilocode_change start import { splitDiffHunks } from "@/kilocode/tui/diff" import { session as banner } from "@/kilocode/cli/logo" import { formatMarkdownTables } from "../../util/markdown" import { submitFeedback } from "@/kilocode/cli/cmd/tui/feedback" -// kilocode_change end +import { nextThinkingMode, reasoningTitle, useThinkingMode, type ThinkingMode } from "../../context/thinking" import { getScrollAcceleration } from "../../util/scroll" import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime" import { DialogRetryAction } from "../../component/dialog-retry-action" @@ -113,7 +103,6 @@ import { PathFormatterProvider, usePathFormatter } from "../../context/path-form addDefaultParsers(parsers.parsers) -// kilocode_change start // czcode_change start function buildSkillReportUrl(skillName: string, sql: string, errorMsg: string): string { const title = encodeURIComponent(t("skillReport.issueTitle", { skillName })) @@ -143,7 +132,6 @@ function buildSkillReportUrl(skillName: string, sql: string, errorMsg: string): return `https://github.com/clickzetta/clickzetta-skills/issues/new?title=${title}&body=${body}` } // czcode_change end -// kilocode_change end const GO_UPSELL_FREE_TIER_LAST_SEEN_AT = "go_upsell_last_seen_at" const GO_UPSELL_FREE_TIER_DONT_SHOW = "go_upsell_dont_show" @@ -197,10 +185,8 @@ const sessionBindingCommands = [ "session.message.next", "session.message.previous", "messages.copy", - // kilocode_change start - message feedback "messages.feedback.up", "messages.feedback.down", - // kilocode_change end "session.copy", "session.export", "session.child.first", @@ -213,6 +199,7 @@ const context = createContext<{ width: number sessionID: string conceal: () => boolean + thinkingMode: () => ThinkingMode showThinking: () => boolean showTimestamps: () => boolean showDetails: () => boolean @@ -291,7 +278,6 @@ export function Session() { blockingSuggestions().length > 0 || network().length > 0, ) - // kilocode_change end const pending = createMemo(() => { return messages().findLast((x) => x.role === "assistant" && !x.time.completed)?.id @@ -305,7 +291,9 @@ export function Session() { const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "auto") const [sidebarOpen, setSidebarOpen] = createSignal(false) const [conceal, setConceal] = createSignal(true) - const [showThinking, setShowThinking] = kv.signal("thinking_visibility", true) + const thinking = useThinkingMode() + const thinkingMode = thinking.mode + const showThinking = createMemo(() => true) const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide") const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true) const [showAssistantMetadata, _setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true) @@ -330,7 +318,6 @@ export function Session() { const sdk = useSDK() const editor = useEditorContext() - // kilocode_change start - background processes are scoped to the visible session function processGroup(sessionID: string) { const info = sync.session.get(sessionID) return info?.parentID ?? info?.id ?? sessionID @@ -366,7 +353,6 @@ export function Session() { onCleanup(() => { stopProcesses(processSessionID) }) - // kilocode_change end createEffect(() => { const sessionID = route.sessionID @@ -417,7 +403,6 @@ export function Session() { if (part.id === lastSwitch) return if (part.tool === "plan_enter") { - // kilocode_change local.agent.set("plan") lastSwitch = part.id } @@ -459,7 +444,6 @@ export function Session() { const exit = useExit() - // kilocode_change start createEffect(() => { const title = Locale.truncate(session()?.title ?? "", 50) return exit.message.set(banner(title, session()?.id, UI.Style.TEXT_DIM, UI.Style.TEXT_NORMAL)) @@ -483,7 +467,6 @@ export function Session() { }, ], })) - // kilocode_change end // Helper: Find next visible message boundary in direction const findNextVisibleMessage = (direction: "next" | "prev"): string | null => { @@ -817,7 +800,11 @@ export function Session() { }, }, { - title: showThinking() ? "Hide thinking" : "Show thinking", + title: (() => { + const next = nextThinkingMode(thinkingMode()) + if (next === "hide") return "Collapse thinking" + return "Expand thinking" + })(), value: "session.toggle.thinking", category: "Session", slash: { @@ -825,7 +812,7 @@ export function Session() { aliases: ["toggle-thinking"], }, run: () => { - setShowThinking((prev) => !prev) + thinking.set(nextThinkingMode(thinkingMode())) dialog.clear() }, }, @@ -1023,7 +1010,6 @@ export function Session() { dialog.clear() }, }, - // kilocode_change start - message feedback { title: "Rate last assistant message helpful", value: "messages.feedback.up", @@ -1036,21 +1022,6 @@ export function Session() { category: "Session", run: () => submitFeedback("down", dialog, { toast, session, messages }), }, - // kilocode_change end - { - title: "Rate last assistant message helpful", - value: "messages.feedback.up", - keybind: "messages_feedback_up", - category: "Session", - onSelect: () => submitFeedback("up", dialog, { toast, session, messages }), - }, - { - title: "Rate last assistant message not helpful", - value: "messages.feedback.down", - keybind: "messages_feedback_down", - category: "Session", - onSelect: () => submitFeedback("down", dialog, { toast, session, messages }), - }, { title: "Copy session transcript", value: "session.copy", @@ -1062,16 +1033,14 @@ export function Session() { try { const sessionData = session() if (!sessionData) return - // kilocode_change start - fetch all messages from server instead of truncated sync store const allMessages = await sdk.client.session.messages({ sessionID: sessionData.id }, { throwOnError: true }) const sessionMessages = allMessages.data.map((msg) => ({ info: msg.info, parts: msg.parts, })) - // kilocode_change end const transcript = formatTranscript( sessionData, - sessionMessages, // kilocode_change + sessionMessages, { thinking: showThinking(), toolDetails: showDetails(), @@ -1112,17 +1081,15 @@ export function Session() { if (options === null) return - // kilocode_change start - fetch all messages from server instead of truncated sync store const allMessages = await sdk.client.session.messages({ sessionID: sessionData.id }, { throwOnError: true }) const sessionMessages = allMessages.data.map((msg) => ({ info: msg.info, parts: msg.parts, })) - // kilocode_change end const transcript = formatTranscript( sessionData, - sessionMessages, // kilocode_change + sessionMessages, { thinking: options.thinking, toolDetails: options.toolDetails, @@ -1261,6 +1228,7 @@ export function Session() { }, sessionID: route.sessionID, conceal, + thinkingMode, showThinking, showTimestamps, showDetails, @@ -1293,13 +1261,11 @@ export function Session() { scrollAcceleration={scrollAcceleration()} > - {/* kilocode_change start */} ↳ Initializing... - {/* kilocode_change end */} {(message, index) => ( @@ -1400,7 +1366,6 @@ export function Session() { 0}> - {/* kilocode_change start */} {(request) => ( - {/* kilocode_change end */} @@ -1695,32 +1659,77 @@ const PART_MAPPING = { function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: AssistantMessage }) { const { theme, subtleSyntax } = useTheme() const ctx = use() + // Collapsed by default in hide mode: a single line throughout, so the + // layout never shifts. Click to open the full markdown block, click to close. + const [expanded, setExpanded] = createSignal(false) + const content = createMemo(() => { - // Filter out redacted reasoning chunks from OpenRouter - // OpenRouter sends encrypted reasoning data that appears as [REDACTED] + // OpenRouter encrypts some reasoning blocks; drop the placeholder. return props.part.text.replace("[REDACTED]", "").trim() }) + // Reasoning is finalized when the server sets `time.end` (see processor.ts). + // Flips independently of the parent message completing. + const isDone = createMemo(() => props.part.time.end !== undefined) + const inMinimal = createMemo(() => ctx.thinkingMode() === "hide") + const duration = createMemo(() => { + const end = props.part.time.end + return end === undefined ? 0 : Math.max(0, end - props.part.time.start) + }) + // OpenAI / Copilot / opencode-via-OpenAI emit `**Title**\n\n` summary + // blocks. Surface the title both while streaming and after settling so the + // collapsed line carries real signal, not just a duration. + const title = createMemo(() => reasoningTitle(content())) + + const toggle = () => { + if (!inMinimal()) return + setExpanded((prev) => !prev) + } + return ( - - - - + + + + {/* Full markdown block: `show` mode, or `hide` after the user opens it. */} + + + + + + {/* Settled: ▶ at the start as the click-to-expand cue. */} + + + {"▶ " + + (title() + ? "Thought: " + title() + " · " + Locale.duration(duration()) + : "Thought for " + Locale.duration(duration()))} + + + + + {/* Streaming: leading animated spinner, no disclosure arrow yet — it + snaps in once reasoning settles, signalling "done, click to expand". */} + + {title() ? "Thinking: " + title() : "Thinking"} + + + ) } @@ -1737,6 +1746,7 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess streaming={true} internalBlockMode="top-level" content={props.part.text.trim()} + tableOptions={{ style: "grid" }} conceal={ctx.conceal()} fg={theme.markdownText} bg={theme.background} @@ -1797,7 +1807,6 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess - {/* kilocode_change start */} @@ -1876,9 +1885,7 @@ function GenericTool(props: ToolProps) { return ( {props.tool} {input(props.input)} @@ -1895,7 +1902,6 @@ function GenericTool(props: ToolProps) { {expanded() ? "Click to collapse" : "Click to expand"} - {/* kilocode_change start */} {/* czcode_change start */} {(skillName) => { @@ -1912,7 +1918,6 @@ function GenericTool(props: ToolProps) { }} {/* czcode_change end */} - {/* kilocode_change end */} @@ -2224,7 +2229,6 @@ function WebSearch(props: ToolProps) { ) } -// kilocode_change start function BackgroundProcess(props: ToolProps) { const sync = useSync() const pathFormatter = usePathFormatter() @@ -2381,13 +2385,12 @@ function Edit(props: ToolProps) { const ft = createMemo(() => filetype(props.input.filePath)) const diffContent = createMemo(() => props.metadata.diff) - const hunks = createMemo(() => splitDiffHunks(diffContent() ?? "")) // kilocode_change + const hunks = createMemo(() => splitDiffHunks(diffContent() ?? "")) return ( - {/* kilocode_change start */} {(hunk, i) => ( @@ -2420,7 +2423,6 @@ function Edit(props: ToolProps) { )} - {/* kilocode_change end */} @@ -2447,7 +2449,6 @@ function ApplyPatch(props: ToolProps) { }) function Diff(p: { diff: string; filePath: string }) { - // kilocode_change start const hunks = createMemo(() => splitDiffHunks(p.diff)) return ( @@ -2483,7 +2484,6 @@ function ApplyPatch(props: ToolProps) { ) - // kilocode_change end } function title(file: { type: string; relativePath: string; filePath: string; deletions: number }) { diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx index a791aebc30..700735d38c 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -38,6 +38,11 @@ export interface DialogSelectProps { disabled?: boolean onTrigger: (option: DialogSelectOption) => void }[] + footerHints?: { + title: string + label: string + side?: "left" | "right" + }[] bindings?: readonly Binding[] current?: T } @@ -334,11 +339,12 @@ export function DialogSelect(props: DialogSelectProps) { } props.ref?.(ref) - const visibleActions = createMemo(() => - actions() + const visibleActions = createMemo(() => [ + ...actions() .map((item) => ({ ...item, label: actionLabels().get(item.command) ?? "" })) .filter((item) => !item.disabled && item.label), - ) + ...(props.footerHints ?? []), + ]) const left = createMemo(() => visibleActions().filter((item) => item.side !== "right")) const right = createMemo(() => visibleActions().filter((item) => item.side === "right")) diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 51462b0f90..218bb23bab 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -2,7 +2,6 @@ import { Installation } from "@/installation" import { Server } from "@/server/server" import * as Log from "@opencode-ai/core/util/log" import { InstanceRuntime } from "@/project/instance-runtime" -import { WithInstance } from "@/project/with-instance" import { Rpc } from "@/util/rpc" import { upgrade } from "@/cli/upgrade" import { Config } from "@/config/config" @@ -100,12 +99,8 @@ export const rpc = { return { url: server.url.toString() } }, async checkUpgrade(input: { directory: string }) { - await WithInstance.provide({ - directory: input.directory, - fn: async () => { - await upgrade().catch(() => {}) - }, - }) + await InstanceRuntime.load({ directory: input.directory }) + await upgrade().catch(() => {}) }, async reload() { await AppRuntime.runPromise( diff --git a/packages/opencode/src/cli/effect-cmd.ts b/packages/opencode/src/cli/effect-cmd.ts index ada5f8677d..1afc4a9664 100644 --- a/packages/opencode/src/cli/effect-cmd.ts +++ b/packages/opencode/src/cli/effect-cmd.ts @@ -3,7 +3,7 @@ import { Effect, Schema } from "effect" import { AppRuntime, type AppServices } from "@/effect/app-runtime" import { InstanceStore } from "@/project/instance-store" import { InstanceRef } from "@/effect/instance-ref" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" // kilocode_change import { cmd, type WithDoubleDash } from "./cmd/cmd" /** @@ -83,19 +83,15 @@ export const effectCmd = (opts: EffectCmdOpts) => return } const directory = opts.directory?.(args) ?? process.cwd() - // Two-phase: load ctx, then run body inside Instance.current ALS. - // Effect's InstanceRef is provided via fiber context, but that context is - // lost across `await` inside `Effect.promise(async () => ...)` callbacks - // — when handlers re-enter Effect via `AppRuntime.runPromise(svc.method())` - // there, attach() falls back to Instance.current ALS, which Node preserves - // across awaits. Matches the pre-effectCmd `bootstrap()` behavior. const { store, ctx } = await AppRuntime.runPromise( InstanceStore.Service.use((store) => store.load({ directory }).pipe(Effect.map((ctx) => ({ store, ctx })))), ) try { + // kilocode_change start - preserve legacy instance context across Promise callbacks await Instance.restore(ctx, () => AppRuntime.runPromise(opts.handler(args).pipe(Effect.provideService(InstanceRef, ctx))), ) + // kilocode_change end } finally { await AppRuntime.runPromise(store.dispose(ctx)) } diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index fa182bb2f2..995a064478 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -2,16 +2,9 @@ import { NamedError } from "@opencode-ai/core/util/error" import { errorFormat } from "@/util/error" import { isRecord } from "@/util/record" -interface ErrorLike { - name?: string - _tag?: string - message?: string - data?: Record -} - type ConfigIssue = { message: string; path: string[] } -function isTaggedError(error: unknown, tag: string): boolean { +function isTaggedError(error: unknown, tag: string): error is Record { return isRecord(error) && error._tag === tag } @@ -39,22 +32,27 @@ function configIssues(input: Record): ConfigIssue[] { : [] } -export function FormatError(input: unknown) { +export function FormatError(input: unknown): string | undefined { + if (input instanceof Error && isRecord(input.cause) && "body" in input.cause) { + const formatted = FormatError(input.cause.body) + if (formatted) return formatted + } + // CliError: domain failure surfaced from an effectCmd handler via fail("...") if (isTaggedError(input, "CliError")) { - const data = input as ErrorLike & { exitCode?: number } - if (data.exitCode != null) process.exitCode = data.exitCode - return data.message ?? "" + if (typeof input.exitCode === "number") process.exitCode = input.exitCode + return stringField(input, "message") ?? "" } // MCPFailed: { name: string } if (NamedError.hasName(input, "MCPFailed")) { - return `MCP server "${(input as ErrorLike).data?.name}" failed. Note, opencode does not support MCP authentication yet.` + const data = isRecord(input) && isRecord(input.data) ? stringField(input.data, "name") : undefined + return `MCP server "${data}" failed.` // kilocode_change } // AccountServiceError, AccountTransportError: TaggedErrorClass if (isTaggedError(input, "AccountServiceError") || isTaggedError(input, "AccountTransportError")) { - return (input as ErrorLike).message ?? "" + return stringField(input, "message") ?? "" } // ProviderModelNotFoundError: { providerID: string, modelID: string, suggestions?: string[], modelsEmpty?: boolean } // kilocode_change @@ -64,11 +62,11 @@ export function FormatError(input: unknown) { ? providerModelNotFound.suggestions.filter((x) => typeof x === "string") : [] return [ - `Model not found: ${providerModelNotFound.providerID}/${providerModelNotFound.modelID}`, + `Model not found: ${stringField(providerModelNotFound, "providerID")}/${stringField(providerModelNotFound, "modelID")}`, ...(suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []), ...(providerModelNotFound.modelsEmpty === true ? ["No models are currently available."] : []), // kilocode_change - `Try: \`kilo models\` to list available models`, - `Or check your config (opencode.json) provider/model names`, + `Try: \`kilo models\` to list available models`, // kilocode_change + `Or check your kilo.json provider/model names`, // kilocode_change ].join("\n") } @@ -113,6 +111,7 @@ export function FormatError(input: unknown) { if (isTaggedError(input, "UICancelledError") || NamedError.hasName(input, "UICancelledError")) { return "" } + return undefined } export function FormatUnknownError(input: unknown): string { diff --git a/packages/opencode/src/cli/upgrade.ts b/packages/opencode/src/cli/upgrade.ts index f177ce841a..00ca70fb33 100644 --- a/packages/opencode/src/cli/upgrade.ts +++ b/packages/opencode/src/cli/upgrade.ts @@ -1,9 +1,9 @@ -import { Bus } from "@/bus" import { Config } from "@/config/config" import { AppRuntime } from "@/effect/app-runtime" import { Flag } from "@opencode-ai/core/flag/flag" import { Installation } from "@/installation" import { InstallationVersion } from "@opencode-ai/core/installation/version" +import { GlobalBus } from "@/bus/global" export async function upgrade() { const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.getGlobal())) @@ -16,7 +16,13 @@ export async function upgrade() { if (!latest) return if (Flag.KILO_ALWAYS_NOTIFY_UPDATE) { - await Bus.publish(Installation.Event.UpdateAvailable, { version: latest }) + GlobalBus.emit("event", { + directory: "global", + payload: { + type: Installation.Event.UpdateAvailable.type, + properties: { version: latest }, + }, + }) return } @@ -25,11 +31,25 @@ export async function upgrade() { const kind = Installation.getReleaseType(InstallationVersion, latest) if (config.autoupdate === "notify" || kind !== "patch") { - await Bus.publish(Installation.Event.UpdateAvailable, { version: latest }) + GlobalBus.emit("event", { + directory: "global", + payload: { + type: Installation.Event.UpdateAvailable.type, + properties: { version: latest }, + }, + }) return } await Installation.upgrade(method, latest) - .then(() => Bus.publish(Installation.Event.Updated, { version: latest })) + .then(() => + GlobalBus.emit("event", { + directory: "global", + payload: { + type: Installation.Event.Updated.type, + properties: { version: latest }, + }, + }), + ) .catch(() => {}) } diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 1c92d2aa20..c52f79482d 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" import { EffectBridge } from "@/effect/bridge" -import type { InstanceContext } from "@/project/instance" +import type { InstanceContext } from "@/project/instance-context" import { SessionID, MessageID } from "@/session/schema" import { Effect, Layer, Context, Schema } from "effect" import { Config } from "@/config/config" diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 2ff636a166..7191e89038 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -1,11 +1,9 @@ export * as ConfigAgent from "./agent" import path from "path" // kilocode_change -import { Exit, Schema, SchemaGetter } from "effect" -import { Bus } from "@/bus" +import { Schema, SchemaGetter } from "effect" import { PositiveInt } from "@opencode-ai/core/schema" import * as Log from "@opencode-ai/core/util/log" -import { NamedError } from "@opencode-ai/core/util/error" import { Glob } from "@opencode-ai/core/util/glob" import { configEntryNameFromPath } from "./entry-name" import { ConfigError } from "./error" @@ -15,6 +13,8 @@ import { ConfigParse } from "./parse" import { ConfigPermission } from "./permission" import { ConfigVariable } from "./variable" // kilocode_change // kilocode_change start +import { Bus } from "@/bus" +import { NamedError } from "@opencode-ai/core/util/error" import { KilocodeConfig } from "@/kilocode/config/config" import type { Warning } from "./config" // kilocode_change end @@ -138,14 +138,18 @@ export async function load(dir: string, warnings?: Warning[]) { // kilocode_change start if (warnings) warnings.push({ path: item, message }) try { - const { Session } = await import("@/session/session") - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) - } catch (e) { - log.warn("could not publish session error", { message, err: e }) + const { capture } = await import("@/kilocode/instance") + const ctx = capture() + if (ctx) { + const { Session } = await import("@/session/session") + await Bus.publish(ctx, Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + } + } catch (error) { + log.warn("could not publish session error", { message, err: error }) } + // kilocode_change end log.error("failed to load agent", { agent: item, err }) return undefined - // kilocode_change end }) if (!md) continue @@ -210,14 +214,18 @@ export async function loadMode(dir: string, warnings?: Warning[]) { // kilocode_change start if (warnings) warnings.push({ path: item, message }) try { - const { Session } = await import("@/session/session") - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) - } catch (e) { - log.warn("could not publish session error", { message, err: e }) + const { capture } = await import("@/kilocode/instance") + const ctx = capture() + if (ctx) { + const { Session } = await import("@/session/session") + await Bus.publish(ctx, Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + } + } catch (error) { + log.warn("could not publish session error", { message, err: error }) } + // kilocode_change end log.error("failed to load mode", { mode: item, err }) return undefined - // kilocode_change end }) if (!md) continue diff --git a/packages/opencode/src/config/command.ts b/packages/opencode/src/config/command.ts index 92a044813e..d83aaac407 100644 --- a/packages/opencode/src/config/command.ts +++ b/packages/opencode/src/config/command.ts @@ -2,13 +2,13 @@ export * as ConfigCommand from "./command" import * as Log from "@opencode-ai/core/util/log" import { Cause, Exit, Schema, SchemaIssue } from "effect" -import { NamedError } from "@opencode-ai/core/util/error" import { Glob } from "@opencode-ai/core/util/glob" -import { Bus } from "@/bus" import { configEntryNameFromPath } from "./entry-name" import * as ConfigMarkdown from "./markdown" import { ConfigModelID } from "./model-id" // kilocode_change start +import { Bus } from "@/bus" +import { NamedError } from "@opencode-ai/core/util/error" import { KilocodeConfig } from "@/kilocode/config/config" import type { Warning } from "./config" // kilocode_change end @@ -44,14 +44,18 @@ export async function load(dir: string, warnings?: Warning[]) { // kilocode_change start if (warnings) warnings.push({ path: item, message }) try { - const { Session } = await import("@/session/session") - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) - } catch (e) { - log.warn("could not publish session error", { message, err: e }) + const { capture } = await import("@/kilocode/instance") + const ctx = capture() + if (ctx) { + const { Session } = await import("@/session/session") + await Bus.publish(ctx, Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + } + } catch (error) { + log.warn("could not publish session error", { message, err: error }) } + // kilocode_change end log.error("failed to load command", { command: item, err }) return undefined - // kilocode_change end }) if (!md) continue diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 099fd4a5af..5e642439ab 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -9,14 +9,11 @@ import { NamedError } from "@opencode-ai/core/util/error" import { Flag } from "@opencode-ai/core/flag/flag" import { Auth } from "../auth" import { Env } from "../env" -import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser" // kilocode_change - parseTree/findNodeAtLocation used in patchJsonc -import { type InstanceContext } from "../project/instance" +import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser" import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version" import { existsSync } from "fs" -// kilocode_change start import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" -// kilocode_change end import { Account } from "@/account/account" import { isRecord } from "@/util/record" import type { ConsoleState } from "./console-state" @@ -24,7 +21,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { InstanceState } from "@/effect/instance-state" import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" -import { containsPath } from "../project/instance-context" +import { containsPath, type InstanceContext } from "../project/instance-context" import { NonNegativeInt, PositiveInt, type DeepMutable } from "@opencode-ai/core/schema" import { ConfigAgent } from "./agent" import { ConfigAttachment } from "./attachment" @@ -45,8 +42,7 @@ import { ConfigServer } from "./server" import { ConfigSkills } from "./skills" import { ConfigVariable } from "./variable" import { Npm } from "@opencode-ai/core/npm" -import z from "zod" // kilocode_change - Kilo config compatibility schemas -// kilocode_change start +import z from "zod" import { ZodOverride } from "@opencode-ai/core/effect-zod" import { KilocodeConfig } from "../kilocode/config/config" import { KilocodeDefaultPlugins } from "@/kilocode/config/default-plugins" @@ -57,12 +53,12 @@ import { } from "@kilocode/kilo-indexing/config" // czcode_change start — builtin commands (skill-fix, skill-update) import { BUILTIN_COMMANDS } from "@/kilocode/commands/builtin" + const builtinCommandMap = Object.fromEntries( - BUILTIN_COMMANDS.map((c) => [c.name, { template: c.template, description: c.description, subtask: c.subtask }]) + BUILTIN_COMMANDS.map((c) => [c.name, { template: c.template, description: c.description, subtask: c.subtask }]), ) // czcode_change end import { unique } from "remeda" -// kilocode_change end const log = Log.create({ service: "config" }) @@ -82,7 +78,7 @@ function mergeConfigConcatArrays(target: Info, source: Info): Info { function normalizeLoadedConfig(data: unknown, source: string) { if (!isRecord(data)) return data - const copy = KilocodeConfig.retireIndexingFlag({ ...data }, source) // kilocode_change + const copy = KilocodeConfig.retireIndexingFlag({ ...data }, source) const hadLegacy = "theme" in copy || "keybinds" in copy || "tui" in copy if (!hadLegacy) return copy delete copy.theme @@ -92,7 +88,6 @@ function normalizeLoadedConfig(data: unknown, source: string) { return copy } -// kilocode_change start export const Warning = z.object({ path: z.string(), message: z.string(), @@ -101,7 +96,6 @@ export const Warning = z.object({ export type Warning = z.infer const { caught: caughtWarning } = KilocodeConfig -// kilocode_change end async function substituteWellKnownRemoteConfig(input: { value: unknown; dir: string; source: string }) { if (!isRecord(input.value) || typeof input.value.url !== "string") return @@ -145,18 +139,16 @@ async function resolveLoadedPlugins( export type Layout = ConfigLayout.Layout -// kilocode_change start - indexing configuration export const Indexing = KiloIndexingConfig export type Indexing = z.infer -// kilocode_change end const LogLevelRef = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({ identifier: "LogLevel", description: "Log level", }) -const Percent = Schema.Number.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)) // kilocode_change +const Percent = Schema.Number.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)) -const IndexingRef = KiloIndexingSchema.annotate({ [ZodOverride]: KiloIndexingConfig }) // kilocode_change +const IndexingRef = KiloIndexingSchema.annotate({ [ZodOverride]: KiloIndexingConfig }) export const Info = Schema.Struct({ $schema: Schema.optional(Schema.String).annotate({ @@ -167,10 +159,10 @@ export const Info = Schema.Struct({ }), logLevel: Schema.optional(LogLevelRef).annotate({ description: "Log level" }), server: Schema.optional(ConfigServer.Server).annotate({ - description: "Server configuration for the kilo serve command", // kilocode_change + description: "Server configuration for the kilo serve command", }), command: Schema.optional(Schema.Record(Schema.String, ConfigCommand.Info)).annotate({ - description: "Command configuration, see https://opencode.ai/docs/commands", + description: "Command configuration, see https://opencode.ai/docs/commands", // kilocode_change }), skills: Schema.optional(ConfigSkills.Info).annotate({ description: "Additional skill folder paths" }), reference: Schema.optional(ConfigReference.Info).annotate({ @@ -204,7 +196,6 @@ export const Info = Schema.Struct({ enabled_providers: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({ description: "When set, ONLY these providers will be enabled. All other providers will be ignored", }), - // kilocode_change start // NOTE: Any new kilocode_change key added to Config.Info must also be mirrored in // apps/web/src/app/config.json/extras.ts in the cloud repo, otherwise // $schema: https://app.kilo.ai/config.json will not recognize it. @@ -243,11 +234,16 @@ export const Info = Schema.Struct({ subagent_variant: Schema.optional(Schema.NullOr(Schema.String)).annotate({ description: "Default model variant for task-tool subagents when subagent_model is configured.", }), + subagent_variant_overrides: Schema.optional( + Schema.NullOr(Schema.Record(Schema.String, Schema.NullOr(Schema.String))), + ).annotate({ + description: + "Model-specific variant overrides for task-tool subagents, keyed by provider/model. Valid overrides take precedence over saved, agent-specific, and inherited variants.", + }), default_agent: Schema.optional(Schema.NullOr(Schema.String)).annotate({ description: "Default agent to use when none is specified. Must be a primary agent. Falls back to 'code' if not set or if the specified agent is invalid.", }), - // kilocode_change end username: Schema.optional(Schema.String).annotate({ description: "Custom username to display in conversations instead of system username", }), @@ -266,11 +262,9 @@ export const Info = Schema.Struct({ // primary plan: Schema.optional(ConfigAgent.Info), build: Schema.optional(ConfigAgent.Info), - // kilocode_change start debug: Schema.optional(ConfigAgent.Info), orchestrator: Schema.optional(ConfigAgent.Info), ask: Schema.optional(ConfigAgent.Info), - // kilocode_change end // subagent general: Schema.optional(ConfigAgent.Info), explore: Schema.optional(ConfigAgent.Info), @@ -320,7 +314,7 @@ export const Info = Schema.Struct({ url: Schema.optional(Schema.String).annotate({ description: "Enterprise URL" }), }), ), - commit_message: KilocodeConfig.CommitMessageSchema, // kilocode_change + commit_message: KilocodeConfig.CommitMessageSchema, tool_output: Schema.optional( Schema.Struct({ max_lines: Schema.optional(PositiveInt).annotate({ @@ -339,12 +333,10 @@ export const Info = Schema.Struct({ auto: Schema.optional(Schema.Boolean).annotate({ description: "Enable automatic compaction when context is full (default: true)", }), - // kilocode_change start threshold_percent: Schema.optional(Schema.NullOr(Percent)).annotate({ description: "Percentage of the model input/context window that triggers automatic compaction. The reserved safety buffer still applies if it would compact sooner.", }), - // kilocode_change end prune: Schema.optional(Schema.Boolean).annotate({ description: "Enable pruning of old tool outputs (default: true)", }), @@ -364,17 +356,13 @@ export const Info = Schema.Struct({ Schema.Struct({ disable_paste_summary: Schema.optional(Schema.Boolean), batch_tool: Schema.optional(Schema.Boolean).annotate({ description: "Enable the batch tool" }), - // kilocode_change start codebase_search: Schema.optional(Schema.Boolean).annotate({ description: "Enable AI-powered codebase search" }), speech_to_text_model: Schema.optional(Schema.String).annotate({ description: "Speech-to-text transcription model ID to use for voice input", }), - // kilocode_change end - // kilocode_change start - enable telemetry by default openTelemetry: Schema.Boolean.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(true))).annotate({ description: "Enable telemetry. Set to false to opt-out.", }), - // kilocode_change end primary_tools: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({ description: "Tools that should only be available to primary agents.", }), @@ -401,7 +389,7 @@ type State = { config: Info directories: string[] deps: Fiber.Fiber[] - warnings: Warning[] // kilocode_change + warnings: Warning[] consoleState: ConsoleState } @@ -410,24 +398,20 @@ export interface Interface { readonly getGlobal: () => Effect.Effect readonly getConsoleState: () => Effect.Effect readonly update: (config: Info) => Effect.Effect - // kilocode_change start readonly updateGlobal: ( config: Info, options?: { dispose?: boolean }, ) => Effect.Effect<{ info: Info; changed: boolean }> - // kilocode_change end readonly invalidate: () => Effect.Effect readonly directories: () => Effect.Effect readonly waitForDependencies: () => Effect.Effect - readonly warnings: () => Effect.Effect // kilocode_change + readonly warnings: () => Effect.Effect } export class Service extends Context.Service()("@opencode/Config") {} function globalConfigFile() { - // kilocode_change start const candidates = ["czcode.jsonc", "czcode.json", "kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json", "config.json"].map((file) => // czcode_change - czcode config files take precedence - // kilocode_change end path.join(Global.Path.config, file), ) for (const file of candidates) { @@ -439,7 +423,6 @@ function globalConfigFile() { function patchJsonc(input: string, patch: unknown, path: string[] = []): string { if (!isRecord(patch)) { const edits = modify(input, path, patch === null ? undefined : patch, { - // kilocode_change formattingOptions: { insertSpaces: true, tabSize: 2, @@ -448,7 +431,6 @@ function patchJsonc(input: string, patch: unknown, path: string[] = []): string return applyEdits(input, edits) } - // kilocode_change start — when the existing JSONC node at this path is a // scalar (e.g. permission.bash is "ask" as a string), jsonc-parser cannot // add child keys to it. Detect this case and replace the whole node with // the patch object in a single modify() call instead of recursing. @@ -466,7 +448,6 @@ function patchJsonc(input: string, patch: unknown, path: string[] = []): string return applyEdits(input, edits) } } - // kilocode_change end return Object.entries(patch).reduce((result, [key, value]) => patchJsonc(result, value, [...path, key]), input) } @@ -516,10 +497,8 @@ export const layer = Layer.effect( yield* Effect.promise(() => resolveLoadedPlugins(data, options.path)) if (!data.$schema) { - // kilocode_change start data.$schema = "https://app.kilo.ai/config.json" const updated = text.replace(/^\s*\{/, '{\n "$schema": "https://app.kilo.ai/config.json",') - // kilocode_change end yield* fs.writeFileString(options.path, updated).pipe(Effect.catch(() => Effect.void)) } return data @@ -532,28 +511,12 @@ export const layer = Layer.effect( return yield* loadConfig(text, { path: filepath }) }) - let globalStamp = "" // kilocode_change + let globalStamp = "" const loadGlobal = Effect.fnUntraced(function* () { - // kilocode_change start yield* Effect.promise(() => KilocodeConfig.migrateBashPermission()) globalStamp = yield* KilocodeGlobalConfigStamp.read(fs, Global.Path.config) - // kilocode_change end - // czcode_change start — seed default skills URL and builtin commands - // Add bundled skills path (next to the binary) for offline use - const bundledSkillsPath = path.join(path.dirname(process.execPath), "clickzetta-skills") - const bundledIncSkillsPath = path.join(path.dirname(process.execPath), "incremental-skills") - const skillsPaths = [bundledSkillsPath, bundledIncSkillsPath].filter((p) => existsSync(p)) - let result: Info = { - skills: { - urls: ["https://clickzetta.github.io/clickzetta-skills/.well-known/skills/"], - paths: skillsPaths, - }, - command: builtinCommandMap, - default_agent: "lh-analyst", - model: "alibaba-coding-plan-cn/glm-5", // czcode_change - glm-5 supports implicit caching on DashScope (qwen3.x does not) - } as Info - // czcode_change end + let result: Info = {} // Seed the default global config with the schema for editor completion, but avoid writing when the user // explicitly routes config through env-provided paths or content. if (!Flag.KILO_CONFIG && !Flag.KILO_CONFIG_DIR && !Flag.KILO_CONFIG_CONTENT) { @@ -565,10 +528,8 @@ export const layer = Layer.effect( } } result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "config.json"))) - // kilocode_change start result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "kilo.json"))) result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "kilo.jsonc"))) - // kilocode_change end result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.json"))) result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.jsonc"))) @@ -579,7 +540,7 @@ export const layer = Layer.effect( .then(async (mod) => { const { provider, model, ...rest } = mod.default if (provider && model) result.model = `${provider}/${model}` - result["$schema"] = "https://app.kilo.ai/config.json" // kilocode_change + result["$schema"] = "https://app.kilo.ai/config.json" result = mergeConfig(result, rest) await fsNode.writeFile(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2)) await fsNode.unlink(legacy) @@ -588,7 +549,7 @@ export const layer = Layer.effect( ) } - globalStamp = yield* KilocodeGlobalConfigStamp.read(fs, Global.Path.config) // kilocode_change + globalStamp = yield* KilocodeGlobalConfigStamp.read(fs, Global.Path.config) return result }) @@ -602,7 +563,6 @@ export const layer = Layer.effect( Duration.infinity, ) - // kilocode_change start - detect global config edits made by other Kilo processes const refreshGlobal = Effect.fnUntraced(function* () { const stamp = yield* KilocodeGlobalConfigStamp.read(fs, Global.Path.config) if (!globalStamp || stamp === globalStamp) return false @@ -610,10 +570,9 @@ export const layer = Layer.effect( yield* invalidateGlobal return true }) - // kilocode_change end const getGlobal = Effect.fn("Config.getGlobal")(function* () { - yield* refreshGlobal() // kilocode_change + yield* refreshGlobal() return yield* cachedGlobal }) @@ -624,7 +583,6 @@ export const layer = Layer.effect( yield* fs .writeFileString( gitignore, - // kilocode_change start - added pnpm-lock.yaml, yarn.lock, agent-manager.json (not in upstream) [ "node_modules", "package.json", @@ -635,11 +593,10 @@ export const layer = Layer.effect( ".gitignore", "agent-manager.json", ].join("\n"), - // kilocode_change end ) .pipe( Effect.catchIf( - (e) => e.reason._tag === "PermissionDenied" || e.reason._tag === "NotFound", // kilocode_change - also ignore NotFound (broken symlink/junction on Windows) + (e) => e.reason._tag === "PermissionDenied" || e.reason._tag === "NotFound", () => Effect.void, ), ) @@ -648,11 +605,23 @@ export const layer = Layer.effect( const loadInstanceState = Effect.fn("Config.loadInstanceState")( function* (ctx: InstanceContext) { - // kilocode_change start - warning accumulator and legacy Kilo config const warnings: Warning[] = [] const auth = yield* authSvc.all().pipe(Effect.orDie) - let result: Info = {} + // czcode_change start — seed default skills URL, model, agent, and builtin commands + const bundledSkillsPath = path.join(path.dirname(process.execPath), "clickzetta-skills") + const bundledIncSkillsPath = path.join(path.dirname(process.execPath), "incremental-skills") + const skillsPaths = [bundledSkillsPath, bundledIncSkillsPath].filter((p) => existsSync(p)) + let result: Info = { + skills: { + urls: ["https://clickzetta.github.io/clickzetta-skills/.well-known/skills/"], + paths: skillsPaths, + }, + command: builtinCommandMap, + default_agent: "lh-analyst", + model: "alibaba-coding-plan-cn/glm-5", // czcode_change - glm-5 supports implicit caching on DashScope (qwen3.x does not) + } as Info + // czcode_change end const legacy = yield* Effect.promise(() => KilocodeConfig.loadLegacyConfigs({ projectDir: ctx.directory, @@ -667,7 +636,6 @@ export const layer = Layer.effect( result = mergeConfigConcatArrays(result, { agent: orgModes.agents }) } warnings.push(...orgModes.warnings) - // kilocode_change end const consoleManagedProviders = new Set() let activeOrgName: string | undefined @@ -700,19 +668,16 @@ export const layer = Layer.effect( result.plugin_origins = plugins }) - // kilocode_change start const merge = Effect.fnUntraced(function* (source: string, next: Info, kind?: ConfigPlugin.Scope) { const scope = kind ?? (yield* pluginScopeForSource(source)) const scoped = KilocodeConfig.scopeIndexing(next, scope) result = mergeConfigConcatArrays(result, scoped) return yield* mergePluginOrigins(source, scoped.plugin, scope) }) - // kilocode_change end for (const [key, value] of Object.entries(auth)) { if (value.type === "wellknown") { const url = key.replace(/\/+$/, "") - // kilocode_change start const source = `${url}/.well-known/opencode` yield* Effect.gen(function* () { process.env[value.key] = value.token @@ -762,23 +727,19 @@ export const layer = Layer.effect( return Effect.void }), ) - // kilocode_change end } } - // kilocode_change start - capture global config failures as warnings const global = yield* getGlobal().pipe( Effect.catchDefect((err: unknown) => { caughtWarning(warnings, "global config", err) return Effect.succeed({} as Info) }), ) - // kilocode_change end yield* merge(Global.Path.config, global, "global") if (Flag.KILO_CONFIG) { - // kilocode_change start - capture KILO_CONFIG failures as warnings yield* merge( Flag.KILO_CONFIG, yield* loadFile(Flag.KILO_CONFIG).pipe( @@ -788,12 +749,10 @@ export const layer = Layer.effect( }), ), ) - // kilocode_change end log.debug("loaded custom config", { path: Flag.KILO_CONFIG }) } if (!Flag.KILO_DISABLE_PROJECT_CONFIG) { - // kilocode_change start - also discover kilo.json project files for (const name of ["czcode", "kilo", "opencode"] as const) { // czcode_change - also discover czcode.json project files for (const file of yield* ConfigPaths.files(name, ctx.directory, ctx.project.worktree).pipe(Effect.orDie)) { yield* merge( @@ -808,14 +767,13 @@ export const layer = Layer.effect( ) } } - // kilocode_change end } result.agent = result.agent || {} result.mode = result.mode || {} result.plugin = result.plugin || [] - const directories = yield* ConfigPaths.directories(ctx.directory, ctx.project.worktree) // kilocode_change + const directories = yield* ConfigPaths.directories(ctx.directory, ctx.project.worktree) if (Flag.KILO_CONFIG_DIR) { log.debug("loading config from KILO_CONFIG_DIR", { path: Flag.KILO_CONFIG_DIR }) @@ -823,7 +781,6 @@ export const layer = Layer.effect( const deps: Fiber.Fiber[] = [] - // kilocode_change start for (const dir of unique(directories)) { if (KilocodeConfig.isConfigDir(dir, Flag.KILO_CONFIG_DIR)) { for (const file of KilocodeConfig.ALL_CONFIG_FILES) { @@ -843,7 +800,6 @@ export const layer = Layer.effect( result.plugin ??= [] } } - // kilocode_change end yield* ensureGitignore(dir).pipe(Effect.orDie) @@ -870,14 +826,12 @@ export const layer = Layer.effect( ) deps.push(dep) - // kilocode_change start - propagate parse errors to the Warning accumulator result.command = mergeDeep( result.command ?? {}, yield* Effect.promise(() => ConfigCommand.load(dir, warnings)), ) result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.load(dir, warnings))) result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.loadMode(dir, warnings))) - // kilocode_change end // Auto-discovered plugins under `.opencode/plugin(s)` are already local files, so ConfigPlugin.load // returns normalized Specs and we only need to attach origin metadata here. const list = yield* Effect.promise(() => ConfigPlugin.load(dir)) @@ -885,7 +839,6 @@ export const layer = Layer.effect( } if (process.env.KILO_CONFIG_CONTENT) { - // kilocode_change start - capture KILO_CONFIG_CONTENT parse failures as warnings const source = "KILO_CONFIG_CONTENT" yield* merge( source, @@ -901,7 +854,6 @@ export const layer = Layer.effect( ), "local", ) - // kilocode_change end } const activeAccount = Option.getOrUndefined( @@ -944,17 +896,14 @@ export const layer = Layer.effect( } const managedDir = ConfigManaged.managedConfigDir() - // kilocode_change start - include kilo.json/kilo.jsonc in managed dir loading if (existsSync(managedDir)) { for (const file of KilocodeConfig.ALL_CONFIG_FILES) { const source = path.join(managedDir, file) yield* merge(source, yield* loadFile(source), "global") } } - // kilocode_change end // macOS managed preferences (.mobileconfig deployed via MDM) override everything - // kilocode_change start const managed = yield* Effect.promise(() => ConfigManaged.readManagedPreferences()) if (managed) { yield* merge( @@ -966,7 +915,6 @@ export const layer = Layer.effect( "global", ) } - // kilocode_change end for (const [name, mode] of Object.entries(result.mode ?? {})) { result.agent = mergeDeep(result.agent ?? {}, { @@ -1006,15 +954,13 @@ export const layer = Layer.effect( if (Flag.KILO_DISABLE_PRUNE) { result.compaction = { ...result.compaction, prune: false } } - // kilocode_change start — inject Kilo default plugins into both plugin list and origins KilocodeDefaultPlugins.apply(result, { disabled: Flag.KILO_DISABLE_DEFAULT_PLUGINS, log }) - // kilocode_change end return { config: result, directories, deps, - warnings, // kilocode_change + warnings, consoleState: { consoleManagedProviders: Array.from(consoleManagedProviders), activeOrgName, @@ -1032,11 +978,9 @@ export const layer = Layer.effect( ) const get = Effect.fn("Config.get")(function* () { - // kilocode_change start - reload instance config when global config changed elsewhere if (yield* refreshGlobal()) { yield* InstanceState.invalidate(state).pipe(Effect.catchCause(() => Effect.void)) } - // kilocode_change end return yield* InstanceState.use(state, (s) => s.config) }) @@ -1055,7 +999,6 @@ export const layer = Layer.effect( }) const update = Effect.fn("Config.update")(function* (config: Info) { - // kilocode_change start - delegate Kilo project config update behavior. const ctx = yield* InstanceState.context yield* KilocodeConfig.updateProjectConfig({ fs, @@ -1082,16 +1025,13 @@ export const layer = Layer.effect( const warnings = Effect.fn("Config.warnings")(function* () { return yield* InstanceState.use(state, (s) => s.warnings) }) - // kilocode_change end const invalidate = Effect.fn("Config.invalidate")(function* () { yield* invalidateGlobal }) - // kilocode_change start - add dispose option to skip Instance.disposeAll for permission-only changes const updateGlobal = Effect.fn("Config.updateGlobal")(function* (config: Info, options?: { dispose?: boolean }) { const dispose = options?.dispose ?? true - // kilocode_change end const file = globalConfigFile() const before = (yield* readConfigFile(file)) ?? "{}" const patch = writableGlobal(config) @@ -1100,7 +1040,7 @@ export const layer = Layer.effect( let changed: boolean if (!file.endsWith(".jsonc")) { const existing = ConfigParse.schema(Info, ConfigParse.jsonc(before, file), file) - const merged = KilocodeConfig.mergeConfig(writable(existing), patch) // kilocode_change + const merged = KilocodeConfig.mergeConfig(writable(existing), patch) const serialized = JSON.stringify(merged, null, 2) changed = serialized !== before if (changed) yield* fs.writeFileString(file, serialized).pipe(Effect.orDie) @@ -1112,7 +1052,6 @@ export const layer = Layer.effect( if (changed) yield* fs.writeFileString(file, updated).pipe(Effect.orDie) } - // kilocode_change start - skip dispose when caller opts out if (!dispose) { yield* invalidateGlobal yield* InstanceState.invalidate(state).pipe(Effect.catchCause(() => Effect.void)) @@ -1127,10 +1066,8 @@ export const layer = Layer.effect( ).pipe(Effect.catchCause(() => Effect.void)) return { info: next, changed } } - // kilocode_change end if (changed) yield* invalidate() - // kilocode_change start - hot-reload global config changes in the active instance if (changed) { yield* InstanceState.invalidate(state).pipe(Effect.catchCause(() => Effect.void)) yield* Effect.sync(() => @@ -1143,7 +1080,6 @@ export const layer = Layer.effect( }), ).pipe(Effect.catchCause(() => Effect.void)) } - // kilocode_change end return { info: next, changed } }) @@ -1156,7 +1092,7 @@ export const layer = Layer.effect( invalidate, directories, waitForDependencies, - warnings, // kilocode_change + warnings, }) }), ) diff --git a/packages/opencode/src/config/mcp.ts b/packages/opencode/src/config/mcp.ts index d74fc20def..d0964baf4c 100644 --- a/packages/opencode/src/config/mcp.ts +++ b/packages/opencode/src/config/mcp.ts @@ -1,6 +1,7 @@ import { Schema, SchemaGetter } from "effect" // kilocode_change -import { zod } from "@opencode-ai/core/effect-zod" -import { PositiveInt, withStatics } from "@opencode-ai/core/schema" +import { zod } from "@opencode-ai/core/effect-zod" // kilocode_change +import { PositiveInt } from "@opencode-ai/core/schema" +import { withStatics } from "@opencode-ai/core/schema" // kilocode_change const LocalCanonical = Schema.Struct({ // kilocode_change diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts index b6ee7e92a0..e67741d0ce 100644 --- a/packages/opencode/src/config/provider.ts +++ b/packages/opencode/src/config/provider.ts @@ -12,7 +12,7 @@ export const Model = Schema.Struct({ ai_sdk_provider: Schema.optional(Schema.Literals(AI_SDK_PROVIDERS)), // kilocode_change release_date: Schema.optional(Schema.String), attachment: Schema.optional(Schema.Boolean), - reasoning: Schema.optional(Schema.Boolean), + reasoning: Schema.optional(Schema.NullOr(Schema.Boolean)), // kilocode_change - allow null so reasoning can be removed via stripNulls on save temperature: Schema.optional(Schema.Boolean), tool_call: Schema.optional(Schema.Boolean), interleaved: Schema.optional( diff --git a/packages/opencode/src/control-plane/adapters/worktree.ts b/packages/opencode/src/control-plane/adapters/worktree.ts index 1c85d125a2..87e30e1139 100644 --- a/packages/opencode/src/control-plane/adapters/worktree.ts +++ b/packages/opencode/src/control-plane/adapters/worktree.ts @@ -1,5 +1,6 @@ -import { Schema } from "effect" -import { type WorkspaceAdapter, WorkspaceInfo } from "../types" +import { Effect, Schema } from "effect" +import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref" +import { type WorkspaceAdapter, type WorkspaceAdapterContext, WorkspaceInfo } from "../types" const WorktreeConfig = Schema.Struct({ name: WorkspaceInfo.fields.name, @@ -9,53 +10,81 @@ const WorktreeConfig = Schema.Struct({ const decodeWorktreeConfig = Schema.decodeUnknownSync(WorktreeConfig) async function loadWorktree() { - const [{ AppRuntime }, { Instance }, { Worktree }] = await Promise.all([ - import("@/effect/app-runtime"), - import("@/project/instance"), - import("@/worktree"), - ]) - return { AppRuntime, Instance, Worktree } + const [{ AppRuntime }, { Worktree }] = await Promise.all([import("@/effect/app-runtime"), import("@/worktree")]) + return { AppRuntime, Worktree } } +function requireInstance(context: WorkspaceAdapterContext | undefined) { + if (!context?.instance) throw new Error("Worktree adapter requires an instance context") + return context.instance +} + +const provideContext = (effect: Effect.Effect, context: WorkspaceAdapterContext | undefined) => + effect.pipe( + Effect.provideService(InstanceRef, requireInstance(context)), + Effect.provideService(WorkspaceRef, context?.workspaceID), + ) + export const WorktreeAdapter: WorkspaceAdapter = { name: "Worktree", description: "Create a git worktree", - async configure(info) { + async configure(info, context) { const { AppRuntime, Worktree } = await loadWorktree() - const next = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.makeWorktreeInfo({ detached: true }))) + const next = await AppRuntime.runPromise( + provideContext( + Worktree.Service.use((svc) => svc.makeWorktreeInfo({ detached: true })), + context, + ), + ) return { ...info, name: next.name, directory: next.directory, } }, - async create(info) { + async create(info, _env, _from, context) { const { AppRuntime, Worktree } = await loadWorktree() const config = decodeWorktreeConfig(info) await AppRuntime.runPromise( - Worktree.Service.use((svc) => - svc.createFromInfo({ - name: config.name, - directory: config.directory, - ...(config.branch ? { branch: config.branch } : {}), - }), + provideContext( + Worktree.Service.use((svc) => + svc.createFromInfo({ + name: config.name, + directory: config.directory, + ...(config.branch ? { branch: config.branch } : {}), + }), + ), + context, ), ) }, - async list() { - const { AppRuntime, Instance, Worktree } = await loadWorktree() - return (await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.list()))).map((info) => ({ + async list(context) { + const { AppRuntime, Worktree } = await loadWorktree() + const ctx = requireInstance(context) + return ( + await AppRuntime.runPromise( + provideContext( + Worktree.Service.use((svc) => svc.list()), + context, + ), + ) + ).map((info) => ({ type: "worktree", name: info.name, branch: info.branch, directory: info.directory, - projectID: Instance.project.id, + projectID: ctx.project.id, })) }, - async remove(info) { + async remove(info, context) { const { AppRuntime, Worktree } = await loadWorktree() const config = decodeWorktreeConfig(info) - await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove({ directory: config.directory }))) + await AppRuntime.runPromise( + provideContext( + Worktree.Service.use((svc) => svc.remove({ directory: config.directory })), + context, + ), + ) }, target(info) { const config = decodeWorktreeConfig(info) diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts index e55ae2194e..daa8374530 100644 --- a/packages/opencode/src/control-plane/types.ts +++ b/packages/opencode/src/control-plane/types.ts @@ -1,5 +1,6 @@ import { Schema, Struct } from "effect" import { ProjectID } from "@/project/schema" +import type { InstanceContext } from "@/project/instance-context" import { WorkspaceID } from "./schema" import type { DeepMutable } from "@opencode-ai/core/schema" @@ -37,12 +38,22 @@ export type Target = headers?: HeadersInit } +export type WorkspaceAdapterContext = { + readonly instance?: InstanceContext + readonly workspaceID?: WorkspaceID +} + export type WorkspaceAdapter = { name: string description: string - configure(info: WorkspaceInfo): WorkspaceInfo | Promise - create(info: WorkspaceInfo, env: Record, from?: WorkspaceInfo): Promise - list?(): WorkspaceListedInfo[] | Promise - remove(info: WorkspaceInfo): Promise - target(info: WorkspaceInfo): Target | Promise + configure(info: WorkspaceInfo, context?: WorkspaceAdapterContext): WorkspaceInfo | Promise + create( + info: WorkspaceInfo, + env: Record, + from?: WorkspaceInfo, + context?: WorkspaceAdapterContext, + ): Promise + list?(context?: WorkspaceAdapterContext): WorkspaceListedInfo[] | Promise + remove(info: WorkspaceInfo, context?: WorkspaceAdapterContext): Promise + target(info: WorkspaceInfo, context?: WorkspaceAdapterContext): Target | Promise } diff --git a/packages/opencode/src/control-plane/workspace-adapter-runtime.ts b/packages/opencode/src/control-plane/workspace-adapter-runtime.ts new file mode 100644 index 0000000000..235edc9d22 --- /dev/null +++ b/packages/opencode/src/control-plane/workspace-adapter-runtime.ts @@ -0,0 +1,51 @@ +import { Effect } from "effect" +import { EffectBridge } from "@/effect/bridge" +import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref" +import { getAdapter } from "./adapters" +import type { WorkspaceAdapter, WorkspaceInfo } from "./types" + +const context = Effect.gen(function* () { + return { + instance: yield* InstanceRef, + workspaceID: yield* WorkspaceRef, + } +}) + +export const target = (info: WorkspaceInfo) => + Effect.gen(function* () { + const adapter = getAdapter(info.projectID, info.type) + const ctx = yield* context + return yield* EffectBridge.fromPromise(() => adapter.target(info, ctx)) + }) + +export const configure = (adapter: WorkspaceAdapter, info: WorkspaceInfo) => + Effect.gen(function* () { + const ctx = yield* context + return yield* EffectBridge.fromPromise(() => adapter.configure(info, ctx)) + }) + +export const create = ( + adapter: WorkspaceAdapter, + info: WorkspaceInfo, + env: Record, + from?: WorkspaceInfo, +) => + Effect.gen(function* () { + const ctx = yield* context + return yield* EffectBridge.fromPromise(() => adapter.create(info, env, from, ctx)) + }) + +export const list = (adapter: WorkspaceAdapter) => + Effect.gen(function* () { + const ctx = yield* context + return yield* EffectBridge.fromPromise(() => Promise.resolve(adapter.list?.(ctx) ?? [])) + }) + +export const remove = (info: WorkspaceInfo) => + Effect.gen(function* () { + const adapter = getAdapter(info.projectID, info.type) + const ctx = yield* context + return yield* EffectBridge.fromPromise(() => adapter.remove(info, ctx)) + }) + +export * as WorkspaceAdapterRuntime from "./workspace-adapter-runtime" diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 3b3775a300..09d7a51e7c 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -26,11 +26,11 @@ import { SessionID } from "@/session/schema" import { NotFoundError } from "@/storage/storage" import { errorData } from "@/util/error" import { waitEvent } from "./util" -import { WorkspaceContext } from "./workspace-context" -import { EffectBridge } from "@/effect/bridge" +import { WorkspaceRef } from "@/effect/instance-ref" import { Vcs } from "@/project/vcs" import { InstanceStore } from "@/project/instance-store" import { InstanceBootstrap } from "@/project/bootstrap" +import { WorkspaceAdapterRuntime } from "./workspace-adapter-runtime" export const Info = Schema.Struct({ ...WorkspaceInfoSchema.fields, @@ -281,8 +281,7 @@ export const layer = Layer.effect( const workspace = yield* get(input.workspaceID) if (!workspace) return input.fallback - const adapter = getAdapter(workspace.projectID, workspace.type) - const target = yield* EffectBridge.fromPromise(() => adapter.target(workspace)) + const target = yield* WorkspaceAdapterRuntime.target(workspace) if (target.type === "local") { const store = yield* InstanceStore.Service @@ -375,35 +374,27 @@ export const layer = Layer.effect( events: events.length, }) - yield* Effect.promise(async () => { - await WorkspaceContext.provide({ - workspaceID: space.id, - async fn() { - await Effect.runPromise( - Effect.forEach( - events, - (event) => - sync.replay( - { - id: event.id, - aggregateID: event.aggregate_id, - seq: event.seq, - type: event.type, - data: event.data, - }, - { publish: true }, - ), - { discard: true }, - ), + yield* Effect.forEach( + events, + (event) => + sync + .replay( + { + id: event.id, + aggregateID: event.aggregate_id, + seq: event.seq, + type: event.type, + data: event.data, + }, + { publish: true }, ) - }, - }) - }) + .pipe(Effect.provideService(WorkspaceRef, space.id)), + { discard: true }, + ) }) const syncWorkspaceLoop = Effect.fn("Workspace.syncWorkspaceLoop")(function* (space: Info) { - const adapter = getAdapter(space.projectID, space.type) - const target = yield* EffectBridge.fromPromise(() => adapter.target(space)) + const target = yield* WorkspaceAdapterRuntime.target(space) if (target.type === "local") return @@ -486,8 +477,7 @@ export const layer = Layer.effect( const startSync = Effect.fn("Workspace.startSync")(function* (space: Info) { if (!flags.experimentalWorkspaces) return - const adapter = getAdapter(space.projectID, space.type) - const target = yield* EffectBridge.fromPromise(() => adapter.target(space)).pipe( + const target = yield* WorkspaceAdapterRuntime.target(space).pipe( Effect.catch((error) => Effect.sync(() => { setStatus(space.id, "error") @@ -538,15 +528,13 @@ export const layer = Layer.effect( const create = Effect.fn("Workspace.create")(function* (input: CreateInput) { const id = WorkspaceID.ascending(input.id) const adapter = getAdapter(input.projectID, input.type) - const config = yield* EffectBridge.fromPromise(() => - adapter.configure({ - ...input, - id, - name: Slug.create(), - directory: null, - extra: input.extra ?? null, - }), - ) + const config = yield* WorkspaceAdapterRuntime.configure(adapter, { + ...input, + id, + name: Slug.create(), + directory: null, + extra: input.extra ?? null, + }) const info: Info = { id, @@ -583,7 +571,7 @@ export const layer = Layer.effect( OTEL_RESOURCE_ATTRIBUTES: process.env.OTEL_RESOURCE_ATTRIBUTES, } - yield* EffectBridge.fromPromise(() => adapter.create(config, env)) + yield* WorkspaceAdapterRuntime.create(adapter, config, env) yield* Effect.all( [ waitEvent({ @@ -622,8 +610,7 @@ export const layer = Layer.effect( if (current?.workspaceID) { const previous = yield* get(current.workspaceID) if (previous) { - const adapter = getAdapter(previous.projectID, previous.type) - const target = yield* EffectBridge.fromPromise(() => adapter.target(previous)) + const target = yield* WorkspaceAdapterRuntime.target(previous) if (target.type === "remote") { yield* syncHistory(previous, target.url, target.headers).pipe( @@ -701,8 +688,7 @@ export const layer = Layer.effect( workspaceID, }) - const adapter = getAdapter(space.projectID, space.type) - const target = yield* EffectBridge.fromPromise(() => adapter.target(space)) + const target = yield* WorkspaceAdapterRuntime.target(space) if (target.type === "local") { yield* sync.run(Session.Event.Updated, { @@ -855,16 +841,14 @@ export const layer = Layer.effect( const discovered = yield* Effect.forEach( registeredAdapters(project.id), ([type, adapter]) => - adapter.list - ? EffectBridge.fromPromise(() => Promise.resolve(adapter.list?.() ?? [])).pipe( - Effect.catchCause((error) => - Effect.sync(() => { - log.warn("workspace adapter list failed", { type, error }) - return [] - }), - ), - ) - : Effect.succeed([]), + WorkspaceAdapterRuntime.list(adapter).pipe( + Effect.catchCause((error) => + Effect.sync(() => { + log.warn("workspace adapter list failed", { type, error }) + return [] + }), + ), + ), { concurrency: "unbounded" }, ).pipe(Effect.map((items) => items.flat())) @@ -937,8 +921,7 @@ export const layer = Layer.effect( const info = fromRow(row) yield* Effect.catchCause( Effect.gen(function* () { - const adapter = getAdapter(info.projectID, row.type) - yield* EffectBridge.fromPromise(() => adapter.remove(info)) + yield* WorkspaceAdapterRuntime.remove(info) }), () => Effect.sync(() => { diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index 9d7af8563f..e3360a5a2d 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -1,4 +1,4 @@ -import { Effect, Layer, ManagedRuntime } from "effect" +import { Layer, ManagedRuntime } from "effect" import { attach } from "./run-service" import * as Observability from "@opencode-ai/core/effect/observability" @@ -15,7 +15,7 @@ import { Storage } from "@/storage/storage" import { Snapshot } from "@/snapshot" import { Plugin } from "@/plugin" import { ModelsDev } from "@opencode-ai/core/models" -import { ModelCache } from "@/provider/model-cache" // kilocode_change +import { ModelCache } from "@/provider/model-cache" import { Provider } from "@/provider/provider" import { ProviderAuth } from "@/provider/auth" import { Agent } from "@/agent/agent" @@ -52,16 +52,16 @@ import { PtyTicket } from "@/pty/ticket" import { Installation } from "@/installation" import { ShareNext } from "@/share/share-next" import { InstanceBootstrap } from "@/project/bootstrap" // czcode_change -import { InstanceStore } from "@/project/instance-store" import { SessionShare } from "@/share/session" import { SyncEvent } from "@/sync" import { Npm } from "@opencode-ai/core/npm" import { memoMap } from "@opencode-ai/core/effect/memo-map" import { DataMigration } from "@/data-migration" import { BackgroundJob } from "@/background/job" +import { EventV2Bridge } from "@/event-v2-bridge" import { RuntimeFlags } from "@/effect/runtime-flags" -export const AppLayer = Layer.mergeAll( +const CoreLayer = Layer.mergeAll( Npm.defaultLayer, AppFileSystem.defaultLayer, Bus.defaultLayer, @@ -75,13 +75,16 @@ export const AppLayer = Layer.mergeAll( Storage.defaultLayer, Snapshot.defaultLayer, Plugin.defaultLayer, - ModelCache.defaultLayer, // kilocode_change + ModelCache.defaultLayer, ModelsDev.defaultLayer, Provider.defaultLayer, ProviderAuth.defaultLayer, Agent.defaultLayer, Skill.defaultLayer, Discovery.defaultLayer, +) + +const SessionLayer = Layer.mergeAll( Question.defaultLayer, Permission.defaultLayer, Todo.defaultLayer, @@ -102,6 +105,9 @@ export const AppLayer = Layer.mergeAll( McpAuth.defaultLayer, Command.defaultLayer, Truncate.defaultLayer, +) + +const FeatureLayer = Layer.mergeAll( ToolRegistry.defaultLayer, Format.defaultLayer, InstanceBootstrap.defaultLayer, // czcode_change - needed by getBootstrapRunEffect @@ -116,8 +122,14 @@ export const AppLayer = Layer.mergeAll( ShareNext.defaultLayer, SessionShare.defaultLayer, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, DataMigration.defaultLayer, -).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer)) +) + +export const AppLayer = Layer.mergeAll(CoreLayer, SessionLayer, FeatureLayer).pipe( + Layer.provideMerge(InstanceLayer.layer), + Layer.provideMerge(Observability.layer), +) const rt = ManagedRuntime.make(AppLayer, { memoMap }) type Runtime = Pick @@ -144,15 +156,3 @@ export const AppRuntime: Runtime = { }, dispose: () => rt.dispose(), } - -let bootstrapRun: Promise> -export function getBootstrapRunEffect(): Promise> { - if (!bootstrapRun) { - bootstrapRun = AppRuntime.runPromise( - Effect.gen(function* () { - return (yield* InstanceBootstrap.Service).run - }), - ) - } - return bootstrapRun -} diff --git a/packages/opencode/src/effect/bridge.ts b/packages/opencode/src/effect/bridge.ts index 16d8f93669..46ba454d37 100644 --- a/packages/opencode/src/effect/bridge.ts +++ b/packages/opencode/src/effect/bridge.ts @@ -1,17 +1,18 @@ -import { Effect, Exit, Fiber } from "effect" +import { Context, Effect, Exit, Fiber } from "effect" import { WorkspaceContext } from "@/control-plane/workspace-context" -import { Instance, type InstanceContext } from "@/project/instance" import type { WorkspaceID } from "@/control-plane/schema" -import { LocalContext } from "@/util/local-context" import { InstanceRef, WorkspaceRef } from "./instance-ref" import { attachWith } from "./run-service" +import { Instance, type InstanceContext } from "@/kilocode/instance" // kilocode_change export interface Shape { readonly promise: (effect: Effect.Effect) => Promise readonly fork: (effect: Effect.Effect) => Fiber.Fiber readonly run: (effect: Effect.Effect) => Effect.Effect + readonly bind: (fn: (...args: Args) => Result) => (...args: Args) => Result } +// kilocode_change start - preserve legacy Kilo contexts across Promise callbacks function restore(instance: InstanceContext | undefined, workspace: WorkspaceID | undefined, fn: () => R): R { if (instance && workspace !== undefined) { return WorkspaceContext.restore(workspace, () => Instance.restore(instance, fn)) @@ -20,57 +21,72 @@ function restore(instance: InstanceContext | undefined, workspace: WorkspaceI if (workspace !== undefined) return WorkspaceContext.restore(workspace, fn) return fn() } +// kilocode_change end + +function captureSync() { + const fiber = Fiber.getCurrent() + const instance = fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined + const workspace = + (fiber ? Context.getReferenceUnsafe(fiber.context, WorkspaceRef) : undefined) ?? WorkspaceContext.workspaceID + return { instance, workspace } +} + +export const bind = (fn: (...args: Args) => Result) => { + const captured = captureSync() + return (...args: Args) => + restore(captured.instance, captured.workspace, () => // kilocode_change + Effect.runSync( + attachWith( + Effect.sync(() => fn(...args)), + captured, + ), + ), + ) +} /** - * Bridge from Effect into a Promise-returning JS callback while installing - * legacy `Instance.context` and `WorkspaceContext` AsyncLocalStorage for - * the duration of the callback. Effect's `InstanceRef`/`WorkspaceRef` do - * not propagate across async/await boundaries inside `Effect.promise(() => - * async fn)` callbacks that re-enter Effect via `AppRuntime.runPromise`, - * but Node's AsyncLocalStorage does. Use this whenever an Effect crosses - * into JS that may itself spawn new Effect runtimes (workspace adapters, - * legacy plugins, etc.). + * Bridge from Effect into a Promise-returning JS callback while preserving + * legacy AsyncLocalStorage contexts for callback code that still reads them. // kilocode_change * - * Mirrors `Effect.promise` but restores legacy ALS first. + * Mirrors `Effect.promise` but restores Kilo compatibility contexts first. // kilocode_change */ export const fromPromise = (fn: () => Promise | T): Effect.Effect => Effect.gen(function* () { - const instance = yield* InstanceRef - const workspace = yield* WorkspaceRef - return yield* Effect.promise(() => Promise.resolve(restore(instance, workspace, () => fn()))) + // kilocode_change start + const captured = captureSync() + const instance = (yield* InstanceRef) ?? captured.instance + const workspace = (yield* WorkspaceRef) ?? captured.workspace + return yield* Effect.promise(() => Promise.resolve(restore(instance, workspace, fn))) + // kilocode_change end }) export function make(): Effect.Effect { return Effect.gen(function* () { const ctx = yield* Effect.context() - const value = yield* InstanceRef - const instance = - value ?? - (() => { - try { - return Instance.current - } catch (err) { - if (!(err instanceof LocalContext.NotFound)) throw err - } - })() - const workspace = (yield* WorkspaceRef) ?? WorkspaceContext.workspaceID - const attach = (effect: Effect.Effect) => attachWith(effect, { instance, workspace }) + const captured = captureSync() + const instance = (yield* InstanceRef) ?? captured.instance + const workspace = (yield* WorkspaceRef) ?? captured.workspace const wrap = (effect: Effect.Effect) => - attach(effect).pipe(Effect.provide(ctx)) as Effect.Effect + attachWith(effect.pipe(Effect.provide(ctx)) as Effect.Effect, { instance, workspace }) return { promise: (effect: Effect.Effect) => - restore(instance, workspace, () => Effect.runPromise(wrap(effect))), + restore(instance, workspace, () => Effect.runPromise(wrap(effect))), // kilocode_change fork: (effect: Effect.Effect) => - restore(instance, workspace, () => Effect.runFork(wrap(effect))), + restore(instance, workspace, () => Effect.runFork(wrap(effect))), // kilocode_change run: (effect: Effect.Effect) => Effect.callback((resume) => { restore(instance, workspace, () => + // kilocode_change Effect.runPromiseExit(wrap(effect)).then((exit) => resume(Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause)), ), ) }), + bind: + (fn: (...args: Args) => Result) => + (...args: Args) => + restore(instance, workspace, () => Effect.runSync(wrap(Effect.sync(() => fn(...args))))), // kilocode_change } satisfies Shape }) } diff --git a/packages/opencode/src/effect/instance-ref.ts b/packages/opencode/src/effect/instance-ref.ts index effc560c58..d95932c2de 100644 --- a/packages/opencode/src/effect/instance-ref.ts +++ b/packages/opencode/src/effect/instance-ref.ts @@ -1,5 +1,5 @@ import { Context } from "effect" -import type { InstanceContext } from "@/project/instance" +import type { InstanceContext } from "@/project/instance-context" import type { WorkspaceID } from "@/control-plane/schema" export const InstanceRef = Context.Reference("~opencode/InstanceRef", { diff --git a/packages/opencode/src/effect/instance-state.ts b/packages/opencode/src/effect/instance-state.ts index e467b6ef28..75916f07de 100644 --- a/packages/opencode/src/effect/instance-state.ts +++ b/packages/opencode/src/effect/instance-state.ts @@ -1,7 +1,6 @@ -import { Effect, Fiber, ScopedCache, Scope, Context } from "effect" +import { Effect, ScopedCache, Scope } from "effect" import * as EffectLogger from "@opencode-ai/core/effect/logger" -import { Instance, type InstanceContext } from "@/project/instance" -import { LocalContext } from "@/util/local-context" +import { capture, type InstanceContext } from "@/kilocode/instance" // kilocode_change import { InstanceRef, WorkspaceRef } from "./instance-ref" import { registerDisposer } from "./instance-registry" import { WorkspaceContext } from "@/control-plane/workspace-context" @@ -13,20 +12,10 @@ export interface InstanceState { readonly cache: ScopedCache.ScopedCache } -export const bind = any>(fn: F): F => { - try { - return Instance.bind(fn) - } catch (err) { - if (!(err instanceof LocalContext.NotFound)) throw err - } - const fiber = Fiber.getCurrent() - const ctx = fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined - if (!ctx) return fn - return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F -} - export const context = Effect.gen(function* () { - return (yield* InstanceRef) ?? Instance.current + const ctx = (yield* InstanceRef) ?? capture() // kilocode_change + if (!ctx) return yield* Effect.die(new Error("InstanceRef not provided")) + return ctx }) export const workspaceID = Effect.gen(function* () { diff --git a/packages/opencode/src/effect/run-service.ts b/packages/opencode/src/effect/run-service.ts index 1f3802e80c..a47601a712 100644 --- a/packages/opencode/src/effect/run-service.ts +++ b/packages/opencode/src/effect/run-service.ts @@ -1,11 +1,11 @@ import { Effect, Fiber, Layer, ManagedRuntime } from "effect" import * as Context from "effect/Context" -import { Instance } from "@/project/instance" -import { LocalContext } from "@/util/local-context" import { InstanceRef, WorkspaceRef } from "./instance-ref" import * as Observability from "@opencode-ai/core/effect/observability" import { WorkspaceContext } from "@/control-plane/workspace-context" -import type { InstanceContext } from "@/project/instance" +import type { InstanceContext } from "@/project/instance-context" +import { context as instanceContext } from "@/project/instance-context" // kilocode_change +import { LocalContext } from "@/util/local-context" // kilocode_change import { memoMap } from "@opencode-ai/core/effect/memo-map" type Refs = { @@ -25,17 +25,20 @@ export function attachWith(effect: Effect.Effect, refs: Refs): export function attach(effect: Effect.Effect): Effect.Effect { const workspace = WorkspaceContext.workspaceID + const fiber = Fiber.getCurrent() + // kilocode_change start - bridge legacy AsyncLocalStorage instance context into Effect runtimes + const current = fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined const instance = (() => { + if (current) return current try { - return Instance.current + return instanceContext.use() } catch (err) { if (!(err instanceof LocalContext.NotFound)) throw err } })() - if (instance && workspace !== undefined) return attachWith(effect, { instance, workspace }) - const fiber = Fiber.getCurrent() return attachWith(effect, { - instance: instance ?? (fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined), + instance, + // kilocode_change end workspace: workspace ?? (fiber ? Context.getReferenceUnsafe(fiber.context, WorkspaceRef) : undefined), }) } diff --git a/packages/opencode/src/effect/runtime-flags.ts b/packages/opencode/src/effect/runtime-flags.ts index be3e198f9f..266140d6e7 100644 --- a/packages/opencode/src/effect/runtime-flags.ts +++ b/packages/opencode/src/effect/runtime-flags.ts @@ -41,6 +41,14 @@ export class Service extends ConfigService.Service()("@opencode/Runtime experimentalEventSystem: enabledByExperimental("KILO_EXPERIMENTAL_EVENT_SYSTEM"), experimentalWorkspaces: enabledByExperimental("KILO_EXPERIMENTAL_WORKSPACES"), experimentalIconDiscovery: enabledByExperimental("KILO_EXPERIMENTAL_ICON_DISCOVERY"), + disableExternalSkills: bool("KILO_DISABLE_EXTERNAL_SKILLS"), + disableLspDownload: bool("KILO_DISABLE_LSP_DOWNLOAD"), + skipMigrations: bool("KILO_SKIP_MIGRATIONS"), + disableClaudeCodePrompt: Config.all({ + broad: bool("KILO_DISABLE_CLAUDE_CODE"), + direct: bool("KILO_DISABLE_CLAUDE_CODE_PROMPT"), + }).pipe(Config.map((flags) => flags.broad || flags.direct)), + outputTokenMax: positiveInteger("KILO_EXPERIMENTAL_OUTPUT_TOKEN_MAX"), bashDefaultTimeoutMs: positiveInteger("KILO_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"), client: Config.string("KILO_CLIENT").pipe(Config.withDefault("cli")), }) {} diff --git a/packages/opencode/src/event-v2-bridge.ts b/packages/opencode/src/event-v2-bridge.ts new file mode 100644 index 0000000000..a118e3393e --- /dev/null +++ b/packages/opencode/src/event-v2-bridge.ts @@ -0,0 +1,99 @@ +// Temporary V2 bridge: core events are the publish path, but the rest of +// opencode and the HTTP event stream still expect legacy bus/sync payloads. +// This layer goes away once consumers subscribe to core EventV2 directly. +import { Bus as ProjectBus } from "@/bus" +import { GlobalBus } from "@/bus/global" +import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref" +import { InstanceStore } from "@/project/instance-store" +import * as EventWire from "@/kilocode/event-wire" // kilocode_change +import { SyncEvent } from "@/sync" +import { EventV2 } from "@opencode-ai/core/event" +import "@opencode-ai/core/catalog" +import "@opencode-ai/core/session-event" +import { Context, Effect, Layer, Option } from "effect" +import { Schema } from "effect" // kilocode_change - encode EventV2 data at legacy boundaries + +export function toSyncDefinition(definition: D) { + const result = { + type: definition.type, + version: definition.version, + aggregate: definition.aggregate, + schema: definition.data, + properties: definition.data, + wire: true, // kilocode_change + } + return result as SyncEvent.Definition +} + +export class Service extends Context.Service()("@opencode/EventV2Bridge") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const events = yield* EventV2.Service + const bus = yield* ProjectBus.Service + const sync = yield* SyncEvent.Service + + // kilocode_change start - legacy bus and SSE consumers require the schema's encoded representation + const publishGlobal = (event: EventV2.Payload, data: unknown) => + Effect.sync(() => { + GlobalBus.emit("event", { + directory: event.location?.directory ?? "global", + workspace: event.location?.workspaceID, + payload: { + id: event.id, + type: event.type, + properties: data, + }, + }) + }) + + const provideEventLocation = (event: EventV2.Payload, data: unknown, effect: Effect.Effect) => { + return Effect.gen(function* () { + const ctx = yield* InstanceRef + if (ctx) return yield* effect + const store = Option.getOrUndefined(yield* Effect.serviceOption(InstanceStore.Service)) + if (!event.location?.directory || !store) return yield* publishGlobal(event, data) + return yield* store.load({ directory: event.location.directory }).pipe( + Effect.flatMap((ctx) => { + const withInstance = effect.pipe(Effect.provideService(InstanceRef, ctx)) + if (!event.location?.workspaceID) return withInstance + return withInstance.pipe(Effect.provideService(WorkspaceRef, event.location.workspaceID)) + }), + ) + }) + } + // kilocode_change end + + const unsubscribe = yield* events.sync((event) => { + const definition = EventV2.registry.get(event.type) + if (!definition) return Effect.void + const data = EventWire.encode(definition.data, event.data) // kilocode_change + const aggregateID = definition.aggregate + ? (event.data as Record)[definition.aggregate] + : undefined + + if (definition.version !== undefined && typeof aggregateID === "string") { + return provideEventLocation(event, data, sync.run(toSyncDefinition(definition), event.data)) // kilocode_change + } + + return provideEventLocation( + event, + // kilocode_change start + data, + bus.publish({ type: definition.type, properties: Schema.toEncoded(definition.data) }, data, { id: event.id }), + // kilocode_change end + ) + }) + yield* Effect.addFinalizer(() => unsubscribe) + return Service.of(events) + }), +) + +export const defaultLayer = layer.pipe( + Layer.provideMerge(EventV2.defaultLayer), + Layer.provide(SyncEvent.defaultLayer), + Layer.provide(ProjectBus.defaultLayer), +) + +export * as EventV2Bridge from "./event-v2-bridge" diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 657a3dcff0..c14a6f7f2a 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -2,10 +2,11 @@ import { Cause, Effect, Layer, Context, Schema } from "effect" // @ts-ignore import { createWrapper } from "@parcel/watcher/wrapper" import type ParcelWatcher from "@parcel/watcher" -import { readdir } from "fs/promises" +import { readdir, realpath } from "fs/promises" import path from "path" import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" +import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" import { Flag } from "@opencode-ai/core/flag/flag" import { Git } from "@/git" @@ -88,18 +89,18 @@ export const layer = Layer.effect( if (!w) return log.info("watcher backend", { directory: ctx.directory, platform: process.platform, backend }) - + const bridge = yield* EffectBridge.make() const subs: ParcelWatcher.AsyncSubscription[] = [] yield* Effect.addFinalizer(() => Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))), ) - const cb: ParcelWatcher.SubscribeCallback = InstanceState.bind((err, evts) => { + const cb: ParcelWatcher.SubscribeCallback = bridge.bind((err, evts) => { if (err) return for (const evt of evts) { - if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" }) - if (evt.type === "update") void Bus.publish(Event.Updated, { file: evt.path, event: "change" }) - if (evt.type === "delete") void Bus.publish(Event.Updated, { file: evt.path, event: "unlink" }) + if (evt.type === "create") void Bus.publish(ctx, Event.Updated, { file: evt.path, event: "add" }) + if (evt.type === "update") void Bus.publish(ctx, Event.Updated, { file: evt.path, event: "change" }) + if (evt.type === "delete") void Bus.publish(ctx, Event.Updated, { file: evt.path, event: "unlink" }) } }) @@ -122,6 +123,7 @@ export const layer = Layer.effect( const cfgIgnores = cfg.watcher?.ignore ?? [] if (yield* Flag.KILO_EXPERIMENTAL_FILEWATCHER) { + // kilocode_change yield* Effect.forkScoped( subscribe(ctx.directory, [...FileIgnore.PATTERNS, ...cfgIgnores, ...protecteds(ctx.directory)]), ) @@ -131,8 +133,14 @@ export const layer = Layer.effect( const result = yield* git.run(["rev-parse", "--git-dir"], { cwd: ctx.worktree, }) - const vcsDir = result.exitCode === 0 ? path.resolve(ctx.worktree, result.text().trim()) : undefined - if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) { + const resolved = result.exitCode === 0 ? path.resolve(ctx.worktree, result.text().trim()) : undefined + const vcsDir = resolved ? yield* Effect.promise(() => realpath(resolved).catch(() => resolved)) : undefined + if ( + vcsDir && + !cfgIgnores.includes(".git") && + !cfgIgnores.includes(vcsDir) && + (!resolved || !cfgIgnores.includes(resolved)) + ) { const ignore = (yield* Effect.promise(() => readdir(vcsDir).catch(() => []))).filter( (entry) => entry !== "HEAD", ) diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index 4c559631f2..27b28c37bc 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -1,5 +1,5 @@ import { Npm } from "@opencode-ai/core/npm" -import type { InstanceContext } from "../project/instance" +import type { InstanceContext } from "../project/instance-context" import { Filesystem } from "@/util/filesystem" import { Process } from "@/util/process" import { which } from "../util/which" diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index ccda636d26..0ac7b3b336 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -3,7 +3,7 @@ import { hideBin } from "yargs/helpers" import { RunCommand } from "./cli/cmd/run" import { GenerateCommand } from "./cli/cmd/generate" import * as Log from "@opencode-ai/core/util/log" -// import { ConsoleCommand } from "./cli/cmd/account" // kilocode_change - reserve `kilo console` for local settings +// import { ConsoleCommand } from "./cli/cmd/account" import { ProvidersCommand } from "./cli/cmd/providers" import { AgentCommand } from "./cli/cmd/agent" import { UpgradeCommand } from "./cli/cmd/upgrade" @@ -40,7 +40,7 @@ import { Heap } from "./cli/heap" import { drizzle } from "drizzle-orm/bun-sqlite" import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process" import { isRecord } from "@/util/record" -import { KiloCli } from "@/kilocode/cli/setup" // kilocode_change +import { KiloCli } from "@/kilocode/cli/setup" const processMetadata = ensureProcessMetadata("main") @@ -68,9 +68,9 @@ function show(out: string) { process.stderr.write(out) } -let cli = yargs(args) // kilocode_change +let cli = yargs(args) .parserConfiguration({ "populate--": true }) - .scriptName("kilo") // kilocode_change + .scriptName("kilo") .wrap(100) .help("help", "show help") .alias("help", "h") @@ -117,7 +117,7 @@ let cli = yargs(args) // kilocode_change run_id: processMetadata.runID, }) - await KiloCli.bootstrap() // kilocode_change - env tagging, telemetry init, legacy auth migration + await KiloCli.bootstrap() const marker = path.join(Global.Path.data, "czcode.db") // czcode_change if (!(await Filesystem.exists(marker))) { @@ -165,7 +165,6 @@ let cli = yargs(args) // kilocode_change .command(RunCommand) .command(GenerateCommand) .command(DebugCommand) - // kilocode_change - upstream account console intentionally not registered; KiloConsole is added by KiloCli.register .command(ProvidersCommand) .command(AgentCommand) .command(UpgradeCommand) @@ -182,10 +181,8 @@ let cli = yargs(args) // kilocode_change .command(PluginCommand) .command(DbCommand) -// kilocode_change start - register Kilo-specific commands after the upstream chain cli = KiloCli.register(cli) cli = cli - // kilocode_change end .fail((msg, err) => { if ( msg?.startsWith("Unknown argument") || @@ -251,7 +248,7 @@ try { } process.exitCode = 1 } finally { - await KiloCli.shutdown() // kilocode_change - telemetry/session-export shutdown + instance disposal + await KiloCli.shutdown() // Some subprocesses don't react properly to SIGTERM and similar signals. // Most notably, some docker-container-based MCP servers don't handle such signals unless diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index e26db89f3b..c822814fbd 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -137,7 +137,7 @@ export const layer: Layer.Layer(input: { directory: string; fn: () => R }): Promise { - const { WithInstance } = await import("@/project/with-instance") - return WithInstance.provide(input) + const { provide } = await import("@/kilocode/instance") + return provide(input) } export namespace KiloSessions { @@ -368,7 +368,7 @@ export namespace KiloSessions { if (ingestDisabled) return if (enabling) return enabling const seq = ++remoteSeq - void Bus.publish(Event.RemoteStatusChanged, { enabled: true, connected: false }) + void Bus.publish(Instance.current, Event.RemoteStatusChanged, { enabled: true, connected: false }) enabling = (async () => { const token = await kilocodeToken() if (!token) { @@ -433,10 +433,10 @@ export namespace KiloSessions { getSessions, log, onOpen: () => { - void Bus.publish(Event.RemoteStatusChanged, { enabled: true, connected: true }) + void Bus.publish(Instance.current, Event.RemoteStatusChanged, { enabled: true, connected: true }) }, onDisconnect: () => { - void Bus.publish(Event.RemoteStatusChanged, { enabled: !!remote, connected: false }) + void Bus.publish(Instance.current, Event.RemoteStatusChanged, { enabled: !!remote, connected: false }) }, onMessage: (msg) => { // Must run inside Instance.provide so Bus.subscribeAll can access @@ -465,11 +465,11 @@ export namespace KiloSessions { remote = { conn, sender, heartbeat } log.info("remote connection enabled", { connected: conn.connected }) Telemetry.trackRemoteConnectionOpened() - void Bus.publish(Event.RemoteStatusChanged, { enabled: true, connected: conn.connected }) + void Bus.publish(Instance.current, Event.RemoteStatusChanged, { enabled: true, connected: conn.connected }) })() .catch((err) => { if (remoteSeq === seq && !remote) - void Bus.publish(Event.RemoteStatusChanged, { enabled: false, connected: false }) + void Bus.publish(Instance.current, Event.RemoteStatusChanged, { enabled: false, connected: false }) throw err }) .finally(() => { @@ -484,14 +484,14 @@ export namespace KiloSessions { const pending = !!enabling enabling = undefined if (!remote) { - if (pending) void Bus.publish(Event.RemoteStatusChanged, { enabled: false, connected: false }) + if (pending) void Bus.publish(Instance.current, Event.RemoteStatusChanged, { enabled: false, connected: false }) return } remote.sender.dispose() remote.conn.close() remote = undefined log.info("remote connection disabled") - void Bus.publish(Event.RemoteStatusChanged, { enabled: false, connected: false }) + void Bus.publish(Instance.current, Event.RemoteStatusChanged, { enabled: false, connected: false }) } export function remoteStatus() { diff --git a/packages/opencode/src/kilo-sessions/remote-sender.ts b/packages/opencode/src/kilo-sessions/remote-sender.ts index 0811826b9a..f108604480 100644 --- a/packages/opencode/src/kilo-sessions/remote-sender.ts +++ b/packages/opencode/src/kilo-sessions/remote-sender.ts @@ -15,11 +15,11 @@ import z from "zod" import { zodObject } from "@opencode-ai/core/effect-zod" import { Effect } from "effect" -type Provide = typeof import("@/project/with-instance").provide +type Provide = typeof import("@/kilocode/instance").provide async function provide(input: { directory: string; fn: () => R }): Promise { - const { WithInstance } = await import("@/project/with-instance") - return WithInstance.provide(input) + const { provide } = await import("@/kilocode/instance") + return provide(input) } const QuestionData = z.object({ diff --git a/packages/opencode/src/kilocode/agent/index.ts b/packages/opencode/src/kilocode/agent/index.ts index 854d4fabf8..901b392e90 100644 --- a/packages/opencode/src/kilocode/agent/index.ts +++ b/packages/opencode/src/kilocode/agent/index.ts @@ -4,7 +4,7 @@ import { NamedError } from "@opencode-ai/core/util/error" import { Glob } from "@opencode-ai/core/util/glob" import * as Truncate from "../../tool/truncate" import { Config } from "../../config/config" -import { Instance } from "../../project/instance" +import { Instance } from "../instance" import { InstanceStore } from "../../project/instance-store" import { makeRuntime } from "@/effect/run-service" import { Schema } from "effect" diff --git a/packages/opencode/src/kilocode/background-process/index.ts b/packages/opencode/src/kilocode/background-process/index.ts index 035e76b15c..3b248c2b6c 100644 --- a/packages/opencode/src/kilocode/background-process/index.ts +++ b/packages/opencode/src/kilocode/background-process/index.ts @@ -3,7 +3,7 @@ import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" import { Identifier } from "@/id/id" -import { Instance, type InstanceContext } from "@/project/instance" +import { Instance, type InstanceContext } from "@/kilocode/instance" import { SessionID } from "@/session/schema" import { Shell } from "@/shell/shell" import { NonNegativeInt, PositiveInt, optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema" @@ -194,7 +194,7 @@ export namespace BackgroundProcess { function emit(active: Active) { Instance.restore(active.ctx, () => { - void Bus.publish(Event.Updated, { info: clone(active.info) }).catch((err) => { + void Bus.publish(active.ctx, Event.Updated, { info: clone(active.info) }).catch((err) => { log.warn("failed to publish process update", { err, id: active.info.id }) }) }) @@ -452,9 +452,11 @@ export namespace BackgroundProcess { active.resolve = undefined if (opts.silent) return await Instance.restore(active.ctx, () => - Bus.publish(Event.Deleted, { sessionID: active.info.sessionID, processID: active.info.id }).catch((err) => { - log.warn("failed to publish process deletion", { err, id: active.info.id }) - }), + Bus.publish(active.ctx, Event.Deleted, { sessionID: active.info.sessionID, processID: active.info.id }).catch( + (err) => { + log.warn("failed to publish process deletion", { err, id: active.info.id }) + }, + ), ) } diff --git a/packages/opencode/src/kilocode/bootstrap.ts b/packages/opencode/src/kilocode/bootstrap.ts index 4a72bbf903..637323abc2 100644 --- a/packages/opencode/src/kilocode/bootstrap.ts +++ b/packages/opencode/src/kilocode/bootstrap.ts @@ -8,7 +8,7 @@ import path from "node:path" import { Bus } from "@/bus" import { SessionExport } from "@/kilocode/session-export" import { createWorkspaceProvider } from "@/kilocode/session-export/workspace-provider" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" import { Identity } from "@kilocode/kilo-telemetry" const log = Log.create({ service: "kilocode-bootstrap" }) diff --git a/packages/opencode/src/kilocode/cli/cmd/roll-call.ts b/packages/opencode/src/kilocode/cli/cmd/roll-call.ts index cf025fdef6..2c932bfde8 100644 --- a/packages/opencode/src/kilocode/cli/cmd/roll-call.ts +++ b/packages/opencode/src/kilocode/cli/cmd/roll-call.ts @@ -1,10 +1,11 @@ import type { Argv } from "yargs" -import { WithInstance } from "../../../project/with-instance" +import { provide } from "../../instance" import { Provider } from "../../../provider/provider" import { ProviderTransform } from "../../../provider/transform" import { cmd } from "../../../cli/cmd/cmd" import { UI } from "../../../cli/ui" import { AppRuntime } from "../../../effect/app-runtime" +import { RuntimeFlags } from "../../../effect/runtime-flags" import { generateText } from "ai" import { randomUUID } from "crypto" @@ -134,6 +135,10 @@ function lang(model: Provider.Model) { return AppRuntime.runPromise(Provider.Service.use((svc) => svc.getLanguage(model))) } +export function outputLimit(model: Provider.Model, outputTokenMax?: number) { + return ProviderTransform.maxOutputTokens(model, outputTokenMax) +} + export async function handle(args: ArgumentsCamelCase) { const load = args.list ?? list @@ -167,7 +172,7 @@ export async function handle(args: ArgumentsCamelCase) { ) } - await WithInstance.provide({ + await provide({ directory: process.cwd(), async fn() { const providers = await load() @@ -285,7 +290,9 @@ async function call( const sessionID = randomUUID() const options = ProviderTransform.options({ model, sessionID }) const providerOptions = ProviderTransform.providerOptions(model, options) - const maxOutputTokens = ProviderTransform.maxOutputTokens(model) + const maxOutputTokens = await AppRuntime.runPromise( + RuntimeFlags.Service.useSync((flags) => outputLimit(model, flags.outputTokenMax)), + ) const temperature = ProviderTransform.temperature(model) const topP = ProviderTransform.topP(model) const topK = ProviderTransform.topK(model) diff --git a/packages/opencode/src/kilocode/config-validation.ts b/packages/opencode/src/kilocode/config-validation.ts index 39eddb6718..681c043f04 100644 --- a/packages/opencode/src/kilocode/config-validation.ts +++ b/packages/opencode/src/kilocode/config-validation.ts @@ -9,7 +9,7 @@ import { Config } from "@/config/config" import { ConfigAgent } from "@/config/agent" import { ConfigCommand } from "@/config/command" import { JsonError } from "@/config/error" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" import { Filesystem } from "@/util/filesystem" export namespace ConfigValidation { diff --git a/packages/opencode/src/kilocode/config/config.ts b/packages/opencode/src/kilocode/config/config.ts index 4fb1970d38..35acc53aad 100644 --- a/packages/opencode/src/kilocode/config/config.ts +++ b/packages/opencode/src/kilocode/config/config.ts @@ -190,8 +190,9 @@ export namespace KilocodeConfig { const err = new ConfigError.InvalidError({ path: item, issues }, { cause }) if (warnings) warnings.push({ path: item, message, detail: text || undefined }) try { - const { Session } = await import("@/session/session") - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + const [{ Session }, { capture }] = await Promise.all([import("@/session/session"), import("@/kilocode/instance")]) + const ctx = capture() + if (ctx) Bus.publish(ctx, Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) } catch (e) { log.warn("could not publish session error", { message, err: e }) } diff --git a/packages/opencode/src/kilocode/event-wire.ts b/packages/opencode/src/kilocode/event-wire.ts new file mode 100644 index 0000000000..adc3589ec5 --- /dev/null +++ b/packages/opencode/src/kilocode/event-wire.ts @@ -0,0 +1,43 @@ +import { DateTime, Schema } from "effect" + +const codec = (schema: Schema.Top) => schema as Schema.Codec + +function wire(value: unknown): unknown { + if (DateTime.isDateTime(value)) return DateTime.toEpochMillis(value) + if (Array.isArray(value)) return value.map(wire) + if (!value || typeof value !== "object") return value + const json = (value as { toJSON?: () => unknown }).toJSON + if (typeof json === "function") { + const result = json.call(value) + if (result !== value) return wire(result) + } + return Object.fromEntries( + Object.entries(value) + .filter(([, item]) => item !== undefined) + .map(([key, item]) => [key, wire(item)]), + ) +} + +function legacy(value: unknown): unknown { + if (!value || typeof value !== "object" || Array.isArray(value)) return value + const timestamp = (value as { timestamp?: unknown }).timestamp + if (typeof timestamp !== "string") return value + const millis = Date.parse(timestamp) + if (!Number.isFinite(millis)) return value + return { ...value, timestamp: millis } +} + +export function encode(schema: S, value: unknown): S["Encoded"] { + const target = codec(schema) + const decoded = Schema.decodeUnknownSync(target)(wire(value)) + return Schema.encodeUnknownSync(target)(decoded) as S["Encoded"] +} + +export function decode(schema: S, value: unknown): S["Type"] { + const target = codec(schema) + try { + return Schema.decodeUnknownSync(target)(value) as S["Type"] + } catch { + return Schema.decodeUnknownSync(target)(legacy(value)) as S["Type"] + } +} diff --git a/packages/opencode/src/kilocode/indexing-worker-client.ts b/packages/opencode/src/kilocode/indexing-worker-client.ts index f99b54d7c8..87d74a916c 100644 --- a/packages/opencode/src/kilocode/indexing-worker-client.ts +++ b/packages/opencode/src/kilocode/indexing-worker-client.ts @@ -120,12 +120,7 @@ export namespace IndexingWorker { type: "request", id: id++, method: "init", - input: { - directory, - root, - config, - lancedbPath: process.env.KILO_LANCEDB_PATH, - }, + input: { directory, root, config, lancedbPath: process.env.KILO_LANCEDB_PATH }, } return call(request, (message) => { if (message.ok && message.method === "init") return message.value diff --git a/packages/opencode/src/kilocode/indexing.ts b/packages/opencode/src/kilocode/indexing.ts index 5c8d73f8c4..63f71922af 100644 --- a/packages/opencode/src/kilocode/indexing.ts +++ b/packages/opencode/src/kilocode/indexing.ts @@ -6,7 +6,7 @@ import { hasIndexingPlugin } from "@kilocode/kilo-indexing/detect" import { IndexingStatus, disabledIndexingStatus } from "@kilocode/kilo-indexing/status" import { Telemetry } from "@kilocode/kilo-telemetry" import { fetchKiloEmbeddingModelCatalog } from "@kilocode/kilo-gateway" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" import { Bus } from "@/bus" import { Config } from "@/config/config" import { AppRuntime } from "@/effect/app-runtime" @@ -221,7 +221,7 @@ export namespace KiloIndexing { const inert = async (current: () => Status): Promise => { const publish = async () => { - await Bus.publish(Event, { status: current() }) + await Bus.publish(Instance.current, Event, { status: current() }) } return { @@ -283,7 +283,7 @@ export namespace KiloIndexing { delivery.task = delivery.task .then(async () => { if (disposed || same(delivery.last, next)) return - await Bus.publish(Event, { status: next }) + await Bus.publish(Instance.current, Event, { status: next }) delivery.last = next }) .catch((err) => { @@ -335,7 +335,7 @@ export namespace KiloIndexing { warnings.set(key, item) void Promise.all( [...workspaces].map((workspaceID) => - WorkspaceContext.provide({ workspaceID, fn: () => Bus.publish(Warning, item) }), + WorkspaceContext.provide({ workspaceID, fn: () => Bus.publish(Instance.current, Warning, item) }), ), ).catch((err) => { log.error("failed to publish indexing warning", { err, workspacePath: dir }) diff --git a/packages/opencode/src/kilocode/instance.ts b/packages/opencode/src/kilocode/instance.ts new file mode 100644 index 0000000000..64e2bb8a1f --- /dev/null +++ b/packages/opencode/src/kilocode/instance.ts @@ -0,0 +1,58 @@ +import { Context, Fiber } from "effect" +import { InstanceRef } from "@/effect/instance-ref" +import { context, type InstanceContext } from "@/project/instance-context" +import { LocalContext } from "@/util/local-context" + +export type { InstanceContext } from "@/project/instance-context" + +function current() { + const ctx = capture() + if (!ctx) throw new LocalContext.NotFound("instance") + return ctx +} + +export const Instance = { + get current() { + return current() + }, + get directory() { + return current().directory + }, + get worktree() { + return current().worktree + }, + get project() { + return current().project + }, + bind unknown>(fn: F): F { + const ctx = capture() + if (!ctx) return fn + return ((...args: Parameters) => context.provide(ctx, () => fn(...args))) as F + }, + restore(ctx: InstanceContext, fn: () => R): R { + return context.provide(ctx, fn) + }, +} + +export function capture() { + try { + return context.use() + } catch (err) { + if (!(err instanceof LocalContext.NotFound)) throw err + } + const fiber = Fiber.getCurrent() + return fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined +} + +export function bind unknown>(fn: F): F { + return Instance.bind(fn) +} + +export async function provide(input: { directory: string; fn: () => R }): Promise { + const runtime = await import("@/effect/app-runtime") + const project = await import("@/project/instance-store") + const ctx = await runtime.AppRuntime.runPromise( + project.InstanceStore.Service.use((store) => store.load({ directory: input.directory })), + ) + return context.provide(ctx, input.fn) +} diff --git a/packages/opencode/src/kilocode/permission/drain.ts b/packages/opencode/src/kilocode/permission/drain.ts index 005b91dd79..acda27dd88 100644 --- a/packages/opencode/src/kilocode/permission/drain.ts +++ b/packages/opencode/src/kilocode/permission/drain.ts @@ -2,6 +2,7 @@ import { Bus } from "@/bus" import { Deferred, Effect } from "effect" import { Permission } from "@/permission" import { ConfigProtection } from "@/kilocode/permission/config-paths" +import { Instance } from "@/kilocode/instance" interface PendingEntry { info: Permission.Request @@ -39,14 +40,14 @@ export function drainCovered( if (!denied && !allowed) continue pending.delete(id) if (denied) { - void Bus.publish(Permission.Event.Replied, { + void Bus.publish(Instance.current, Permission.Event.Replied, { sessionID: entry.info.sessionID, requestID: entry.info.id, reply: "reject", }) yield* Deferred.fail(entry.deferred, new Permission.RejectedError()) } else { - void Bus.publish(Permission.Event.Replied, { + void Bus.publish(Instance.current, Permission.Event.Replied, { sessionID: entry.info.sessionID, requestID: entry.info.id, reply: "always", diff --git a/packages/opencode/src/kilocode/plan-followup.ts b/packages/opencode/src/kilocode/plan-followup.ts index 73098ccd37..69d98b3905 100644 --- a/packages/opencode/src/kilocode/plan-followup.ts +++ b/packages/opencode/src/kilocode/plan-followup.ts @@ -5,7 +5,7 @@ import { TuiEvent } from "@/cli/cmd/tui/event" import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" import { Identifier } from "@/id/id" -import { Instance } from "@/project/instance" +import { Instance, provide } from "@/kilocode/instance" import { Provider } from "@/provider/provider" import { ProviderID, ModelID } from "@/provider/schema" import { Question } from "@/question" @@ -356,9 +356,8 @@ export namespace PlanFollowup { model: input.model, }) const session = await PlanFollowupRuntime.session((svc) => svc.get(input.sessionID)) - const { WithInstance } = await import("@/project/with-instance") - await WithInstance.provide({ + await provide({ directory: session.directory, fn: async () => { // Create the session FIRST so session.created fires immediately while the @@ -370,7 +369,7 @@ export namespace PlanFollowup { pending.set(next.id, ctl) const { AppRuntime } = await import("@/effect/app-runtime") await AppRuntime.runPromise(SessionStatus.Service.use((svc) => svc.set(next.id, { type: "busy" }))) - await Bus.publish(TuiEvent.SessionSelect, { sessionID: next.id }) + await Bus.publish(Instance.current, TuiEvent.SessionSelect, { sessionID: next.id }) const idle = () => AppRuntime.runPromise(SessionStatus.Service.use((svc) => svc.set(next.id, { type: "idle" }))).catch((err) => { @@ -451,7 +450,7 @@ export namespace PlanFollowup { return } - const queue = WithInstance.provide({ + const queue = provide({ directory: next.directory, fn: async () => { if (ctl.signal.aborted) { diff --git a/packages/opencode/src/kilocode/project-id.ts b/packages/opencode/src/kilocode/project-id.ts index 392604a254..02d016d9fd 100644 --- a/packages/opencode/src/kilocode/project-id.ts +++ b/packages/opencode/src/kilocode/project-id.ts @@ -1,5 +1,5 @@ import { Context, Effect, Layer } from "effect" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" import path from "path" diff --git a/packages/opencode/src/kilocode/review/review.ts b/packages/opencode/src/kilocode/review/review.ts index 26b80bd49c..1b497a19b0 100644 --- a/packages/opencode/src/kilocode/review/review.ts +++ b/packages/opencode/src/kilocode/review/review.ts @@ -1,6 +1,6 @@ import { $ } from "bun" import * as Log from "@opencode-ai/core/util/log" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" const log = Log.create({ service: "review" }) diff --git a/packages/opencode/src/kilocode/security/github.ts b/packages/opencode/src/kilocode/security/github.ts new file mode 100644 index 0000000000..97cdfee8a3 --- /dev/null +++ b/packages/opencode/src/kilocode/security/github.ts @@ -0,0 +1,26 @@ +export namespace GitHubSecurity { + export function attachment(value: string) { + if (!URL.canParse(value)) return + + const url = new URL(value) + if (url.origin !== "https://github.com") return + if (url.search || url.hash) return + + const asset = url.pathname.match(/^\/user-attachments\/assets\/([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})$/i) + if (asset) return `https://github.com/user-attachments/assets/${encodeURIComponent(asset[1])}` + + const file = url.pathname.match(/^\/user-attachments\/files\/([0-9]+)\/([^/]+)$/) + if (!file) return + + const name = (() => { + try { + return decodeURIComponent(file[2]) + } catch { + return + } + })() + if (!name || name.includes("/") || name.includes("\\") || name.includes("\0")) return + + return `https://github.com/user-attachments/files/${encodeURIComponent(file[1])}/${encodeURIComponent(name)}` + } +} diff --git a/packages/opencode/src/kilocode/server/httpapi/groups/kilocode.ts b/packages/opencode/src/kilocode/server/httpapi/groups/kilocode.ts index 20c13ae84b..5af99a9c83 100644 --- a/packages/opencode/src/kilocode/server/httpapi/groups/kilocode.ts +++ b/packages/opencode/src/kilocode/server/httpapi/groups/kilocode.ts @@ -48,7 +48,7 @@ export const KilocodeApi = HttpApi.make("kilocode") OpenApi.annotations({ identifier: "kilocode.removeSkill", summary: "Remove a skill", - description: "Remove a skill by deleting its directory from disk and clearing it from cache.", + description: "Remove a skill by deleting its manifest from disk and clearing it from cache.", }), ), HttpApiEndpoint.post("removeAgent", KilocodePaths.removeAgent, { diff --git a/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts b/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts index a9472bc234..a5f77d167c 100644 --- a/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts +++ b/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts @@ -32,7 +32,7 @@ import { Auth } from "@/auth" import { EffectBridge } from "@/effect/bridge" import { Bus } from "@/bus" import { Identifier } from "@/id/id" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" import { InstanceStore } from "@/project/instance-store" import { ModelCache } from "@/provider/model-cache" import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api" @@ -471,7 +471,9 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", MessageTable, PartTable, SessionToRow: Session.toRow, - Bus, + // czcode_change - bridge kilo-gateway's 2-arg publish contract onto the + // 3-arg Bus.publish(ctx, def, props) signature using the active instance + Bus: { publish: (event, payload) => Bus.publish(Instance.current, event as any, payload as any) }, SessionCreatedEvent: Session.Event.Created, Identifier, }), diff --git a/packages/opencode/src/kilocode/server/httpapi/handlers/kilocode.ts b/packages/opencode/src/kilocode/server/httpapi/handlers/kilocode.ts index 3eda47145b..7256487eaf 100644 --- a/packages/opencode/src/kilocode/server/httpapi/handlers/kilocode.ts +++ b/packages/opencode/src/kilocode/server/httpapi/handlers/kilocode.ts @@ -1,14 +1,24 @@ import { Effect } from "effect" -import { HttpApiBuilder } from "effect/unstable/httpapi" +import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi" import * as KiloAgent from "@/kilocode/agent" +import * as KiloSkill from "@/kilocode/skill-remove" +import { Agent } from "@/agent/agent" +import { Config } from "@/config/config" import { EffectBridge } from "@/effect/bridge" +import { InstanceState } from "@/effect/instance-state" import { HeapSnapshot } from "@/kilocode/cli/heap-snapshot" +import { InstanceStore } from "@/project/instance-store" import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api" import { Skill } from "@/skill" import { RemoveAgentPayload, RemoveSkillPayload } from "../groups/kilocode" export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode", (handlers) => Effect.gen(function* () { + const agents = yield* Agent.Service + const skills = yield* Skill.Service + const config = yield* Config.Service + const store = yield* InstanceStore.Service + const heapSnapshot = Effect.fn("KilocodeHttpApi.heapSnapshot")(function* () { return yield* Effect.sync(() => HeapSnapshot.write()) }) @@ -16,14 +26,23 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode" const removeSkill = Effect.fn("KilocodeHttpApi.removeSkill")(function* (ctx: { payload: typeof RemoveSkillPayload.Type }) { - yield* Effect.promise(() => Skill.remove(ctx.payload.location)) + const instance = yield* InstanceState.context + const entries = yield* skills.all() + yield* Effect.tryPromise({ + try: () => KiloSkill.remove(ctx.payload.location, entries), + catch: () => new HttpApiError.BadRequest({}), + }) + yield* store.dispose(instance) return true }) const removeAgent = Effect.fn("KilocodeHttpApi.removeAgent")(function* (ctx: { payload: typeof RemoveAgentPayload.Type }) { + const instance = yield* InstanceState.context + // czcode_change - KiloAgent.remove now resolves the agent + config dirs internally yield* EffectBridge.fromPromise(() => KiloAgent.remove(ctx.payload.name)) + yield* store.dispose(instance) return true }) diff --git a/packages/opencode/src/kilocode/session/compaction-chunks.ts b/packages/opencode/src/kilocode/session/compaction-chunks.ts index 90b9968b1f..ea0b49cff9 100644 --- a/packages/opencode/src/kilocode/session/compaction-chunks.ts +++ b/packages/opencode/src/kilocode/session/compaction-chunks.ts @@ -2,6 +2,7 @@ import { Effect } from "effect" import type { Agent } from "@/agent/agent" import type { Config } from "@/config/config" import type { Provider } from "@/provider/provider" +import { ProviderTransform } from "@/provider/transform" import type { LLM } from "@/session/llm" import { MessageV2 } from "@/session/message-v2" import { usable } from "@/session/overflow" @@ -47,6 +48,7 @@ export namespace KiloCompactionChunks { messages: MessageV2.WithParts[] prompt: string target: MessageV2.Assistant + outputTokenMax?: number updateMessage: UpdateMessage updatePart: Update } @@ -61,12 +63,18 @@ export namespace KiloCompactionChunks { return input.result === "stop" && input.error?.name === "ContextOverflowError" } - export function needed(input: { cfg: Config.Info; model: Provider.Model; tokens: number }) { + export function needed(input: { + cfg: Config.Info + model: Provider.Model + tokens: number + outputTokenMax?: number + }) { + const mdl = model(input.model, input.outputTokenMax) // Apply 1.3x multiplier to token estimate to compensate for Token.estimate // under-counting actual provider tokenizer counts by ~15-30%. return ( - Math.ceil(input.tokens * 1.3) + model(input.model).limit.output > - usable({ cfg: input.cfg, model: model(input.model) }) + Math.ceil(input.tokens * 1.3) + mdl.limit.output > + usable({ cfg: input.cfg, model: mdl, outputTokenMax: input.outputTokenMax }) ) } @@ -76,7 +84,7 @@ export namespace KiloCompactionChunks { index: 0, messages: [{ info: input.replay.info, parts: input.replay.parts }], } - const size = budget({ cfg: input.cfg, model: input.model }) + const size = budget({ cfg: input.cfg, model: input.model, outputTokenMax: input.outputTokenMax }) if (!(yield* large({ messages: chunk.messages, model: input.model, size }))) return input.replay const result = yield* summarize({ ...input, chunk, total: 1 }) if (result.result !== "continue" || !result.output) return input.replay @@ -100,16 +108,21 @@ export namespace KiloCompactionChunks { }) } - function budget(input: { cfg: Config.Info; model: Provider.Model }) { - return Math.max(1_000, Math.floor(usable({ cfg: input.cfg, model: model(input.model) }) * RATIO)) + export function budget(input: { cfg: Config.Info; model: Provider.Model; outputTokenMax?: number }) { + const mdl = model(input.model, input.outputTokenMax) + return Math.max( + 1_000, + Math.floor(usable({ cfg: input.cfg, model: mdl, outputTokenMax: input.outputTokenMax }) * RATIO), + ) } - function model(input: Provider.Model) { + function model(input: Provider.Model, outputTokenMax?: number) { + const cap = Math.min(OUTPUT, outputTokenMax ?? OUTPUT) return { ...input, limit: { ...input.limit, - output: Math.min(input.limit.output, OUTPUT), + output: ProviderTransform.maxOutputTokens(input, cap), }, } satisfies Provider.Model } @@ -239,14 +252,17 @@ export namespace KiloCompactionChunks { function run(input: Input & { data: LLM.StreamInput["messages"]; text: string }) { return Effect.gen(function* () { const msg = yield* input.session.updateMessage(assistant({ base: input.target, sessionID: input.sessionID })) - const mdl = model(input.model) + const mdl = model(input.model, input.outputTokenMax) const worker = yield* input.processors.create({ assistantMessage: msg, sessionID: input.sessionID, model: mdl }) const opts = input.agent.options const agent = { ...input.agent, options: { ...opts, - maxOutputTokens: Math.min(OUTPUT, typeof opts?.maxOutputTokens === "number" ? opts.maxOutputTokens : OUTPUT), + maxOutputTokens: Math.min( + mdl.limit.output, + typeof opts?.maxOutputTokens === "number" ? opts.maxOutputTokens : mdl.limit.output, + ), }, } const out = yield* Effect.gen(function* () { @@ -276,7 +292,7 @@ export namespace KiloCompactionChunks { function summarize(input: Input & { chunk: Chunk; total: number }) { return Effect.gen(function* () { - const size = budget({ cfg: input.cfg, model: input.model }) + const size = budget({ cfg: input.cfg, model: input.model, outputTokenMax: input.outputTokenMax }) const data = (yield* large({ messages: input.chunk.messages, model: input.model, size })) ? [ { @@ -325,7 +341,7 @@ export namespace KiloCompactionChunks { export function process(input: Input) { return Effect.gen(function* () { - const size = budget({ cfg: input.cfg, model: input.model }) + const size = budget({ cfg: input.cfg, model: input.model, outputTokenMax: input.outputTokenMax }) const chunks = yield* split({ messages: input.messages, model: input.model, size }) log.info("fallback", { chunks: chunks.length, concurrency: CONCURRENCY }) diff --git a/packages/opencode/src/kilocode/session/prompt.ts b/packages/opencode/src/kilocode/session/prompt.ts index dc6244d5df..4faf91460d 100644 --- a/packages/opencode/src/kilocode/session/prompt.ts +++ b/packages/opencode/src/kilocode/session/prompt.ts @@ -7,7 +7,7 @@ import { SessionID, PartID } from "@/session/schema" import { MessageV2 } from "@/session/message-v2" import { Session } from "@/session/session" import { Agent } from "@/agent/agent" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" import type { SessionStatus } from "@/session/status" import { Flag } from "@opencode-ai/core/flag/flag" import { PlanFollowup } from "@/kilocode/plan-followup" @@ -19,7 +19,6 @@ import { Question } from "@/question" import { environmentDetails, type EditorContext } from "@/kilocode/editor-context" import { Identifier } from "@/id/id" import { Filesystem } from "@/util/filesystem" -import { InstanceState } from "@/effect/instance-state" import PROMPT_PLAN from "@/session/prompt/plan.txt" import CODE_SWITCH from "@/session/prompt/code-switch.txt" @@ -67,18 +66,18 @@ export namespace KiloSessionPrompt { question: Pick }): Promise<"continue" | "break"> { if (!shouldAskPlanFollowup({ messages: input.messages, abort: input.abort })) return "break" - const ask = InstanceState.bind(PlanFollowup.ask) + const ask = Instance.bind(PlanFollowup.ask) const action = await ask({ sessionID: input.sessionID, messages: input.messages, abort: input.abort, // Keep the request in the listener-local Question service so HTTP replies can resolve it. question: { - ask: InstanceState.bind((request: Parameters[0]) => + ask: Instance.bind((request: Parameters[0]) => Effect.runPromise(input.question.ask(request)), ), - list: InstanceState.bind(() => Effect.runPromise(input.question.list())), - reject: InstanceState.bind((requestID: Parameters[0]) => + list: Instance.bind(() => Effect.runPromise(input.question.list())), + reject: Instance.bind((requestID: Parameters[0]) => Effect.runPromise(input.question.reject(requestID)), ), }, @@ -279,7 +278,7 @@ export namespace KiloSessionPrompt { }) // keep bind(): inside Effect.promise the project context is lost, so Instance.current throws without it - const ctx = InstanceState.bind(() => Instance.current)() + const ctx = Instance.bind(() => Instance.current)() const plan = Session.plan(input.session, ctx) if (mode(input.agent.name) === "plan") { diff --git a/packages/opencode/src/kilocode/skill-remove.ts b/packages/opencode/src/kilocode/skill-remove.ts new file mode 100644 index 0000000000..d541f33e92 --- /dev/null +++ b/packages/opencode/src/kilocode/skill-remove.ts @@ -0,0 +1,37 @@ +import { unlink } from "node:fs/promises" +import path from "node:path" +import { Global } from "@opencode-ai/core/global" +import { Skill } from "@/skill" + +const LEGACY_BUILTIN_LOCATION = "" + +type Info = Pick + +export function builtin(location: string) { + return location === Skill.BUILTIN_LOCATION || location === LEGACY_BUILTIN_LOCATION +} + +export function target(location: string, skills: readonly Info[]) { + if (builtin(location)) throw new Error("cannot remove built-in skill") + + const skill = skills.find((item) => item.location === location) + if (!skill) throw new Error("skill not found in registry") + if (builtin(skill.location)) throw new Error("cannot remove built-in skill") + if (!path.isAbsolute(skill.location)) throw new Error("skill location must be absolute") + + const file = path.resolve(skill.location) + if (path.basename(file) !== "SKILL.md") throw new Error("skill location must reference SKILL.md") + + const cache = path.join(Global.Path.cache, "skills") + const relative = path.relative(cache, file) + if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) { + throw new Error("remove URL-backed skills from configuration") + } + return file +} + +export async function remove(location: string, skills: readonly Info[]) { + const file = target(location, skills) + // Removing only the manifest disables discovery without recursively deleting user files. + await unlink(file) +} diff --git a/packages/opencode/src/kilocode/snapshot/materialize.ts b/packages/opencode/src/kilocode/snapshot/materialize.ts new file mode 100644 index 0000000000..163f50cdbb --- /dev/null +++ b/packages/opencode/src/kilocode/snapshot/materialize.ts @@ -0,0 +1,231 @@ +import { Effect } from "effect" +import path from "path" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import * as Log from "@opencode-ai/core/util/log" +import { Hash } from "@opencode-ai/core/util/hash" + +export namespace KiloSnapshotMaterialize { + const log = Log.create({ service: "snapshot.materialize" }) + + interface Result { + readonly code: number + readonly text: string + readonly stderr: string + } + + export type Git = ( + cmd: string[], + opts?: { cwd?: string; env?: Record; stdin?: string }, + ) => Effect.Effect + + export interface Input { + readonly gitdir: string + readonly git: Git + readonly fs: AppFileSystem.Interface + } + + export const ref = (gitdir: string) => `refs/kilo/materialize/${Hash.fast(path.resolve(gitdir))}` + const snapshotRef = (hash: string, time = Date.now()) => `refs/kilo/snapshots/${time}/${hash}` + + const pack = Effect.fnUntraced(function* (input: Input, dir: string, name: string, objects: string[]) { + if (!objects.length) return true + yield* input.fs.ensureDir(dir).pipe(Effect.catch(() => Effect.void)) + const result = yield* input.git(["--git-dir", input.gitdir, "pack-objects", "--non-empty", path.join(dir, name)], { + stdin: `${objects.join("\n")}\n`, + }) + if (result.code === 0 && result.text.trim()) return true + log.warn("failed to localize snapshot objects", { name, objects: objects.length, stderr: result.stderr }) + return false + }) + + export const pin = Effect.fnUntraced(function* (input: Input, hash: string) { + const result = yield* input.git(["--git-dir", input.gitdir, "update-ref", snapshotRef(hash), hash]) + if (result.code === 0) return true + log.warn("failed to pin snapshot", { hash, stderr: result.stderr }) + return false + }) + + export const localize = Effect.fnUntraced(function* ( + input: Input & { readonly staging: string; readonly seed: string }, + ) { + const diff = yield* input.git([ + "--git-dir", + input.gitdir, + "diff-index", + "--cached", + "--name-only", + "-z", + input.seed, + ]) + if (diff.code !== 0) return false + const files = new Set(diff.text.split("\0").filter(Boolean)) + if (!files.size) return true + + const listed = yield* input.git(["--git-dir", input.gitdir, "ls-files", "--stage", "-z"]) + if (listed.code !== 0) return false + const objects = Array.from( + new Set( + listed.text.split("\0").flatMap((line) => { + const match = line.match(/^(\d+) ([0-9a-f]+) 0\t(.*)$/) + if (!match || match[1] === "160000" || !files.has(match[3])) return [] + return [match[2]] + }), + ), + ) + return yield* pack(input, path.join(input.staging, "pack"), "changed", objects) + }) + + export const localizeTrees = Effect.fnUntraced(function* (input: Input & { readonly staging: string }, hash: string) { + const listed = yield* input.git(["--git-dir", input.gitdir, "ls-tree", "-r", "-t", "-z", hash]) + if (listed.code !== 0) return false + const objects = Array.from( + new Set([ + hash, + ...listed.text.split("\0").flatMap((line) => { + const match = line.match(/^\d+ tree ([0-9a-f]+)\t/) + return match ? [match[1]] : [] + }), + ]), + ) + return yield* pack(input, path.join(input.staging, "pack"), "trees", objects) + }) + + export const prune = Effect.fnUntraced(function* (input: Input, before: number) { + const result = yield* input.git([ + "--git-dir", + input.gitdir, + "for-each-ref", + "--format=%(refname)", + "refs/kilo/snapshots", + ]) + if (result.code !== 0) { + log.warn("failed to list snapshot pins for pruning", { stderr: result.stderr }) + return false + } + const refs = result.text + .split("\n") + .map((item) => item.trim()) + .filter((item) => { + const match = item.match(/^refs\/kilo\/snapshots\/(\d+)\/[0-9a-f]+$/) + if (!match) return false + const time = Number(match[1]) + return Number.isSafeInteger(time) && time < before + }) + if (!refs.length) return true + const removed = yield* input.git(["--git-dir", input.gitdir, "update-ref", "--stdin"], { + stdin: refs.map((item) => `delete ${item}`).join("\n") + "\n", + }) + if (removed.code === 0) return true + log.warn("failed to prune snapshot pins", { refs: refs.length, stderr: removed.stderr }) + return false + }) + + export const run = Effect.fnUntraced(function* (input: Input) { + const started = Date.now() + const alt = path.join(input.gitdir, "objects", "info", "alternates") + const hold = `${alt}.materializing` + if (!(yield* input.fs.exists(alt)) && (yield* input.fs.exists(hold))) yield* input.fs.rename(hold, alt) + if (!(yield* input.fs.exists(alt))) { + yield* Effect.all( + [ + input.fs.remove(`${alt}.seed`).pipe(Effect.catch(() => Effect.void)), + input.fs + .remove(path.join(input.gitdir, "seed-objects"), { recursive: true }) + .pipe(Effect.catch(() => Effect.void)), + ], + { discard: true }, + ) + return false + } + const text = yield* input.fs.readFileString(alt) + + const refs = yield* input.git([ + "--git-dir", + input.gitdir, + "for-each-ref", + "--format=%(objectname)", + "refs/kilo/snapshots", + ]) + if (refs.code !== 0) { + log.warn("failed to list snapshot pins", { stderr: refs.stderr }) + return false + } + const roots = yield* Effect.gen(function* () { + const listed = Array.from( + new Set( + refs.text + .split("\n") + .map((item) => item.trim()) + .filter(Boolean), + ), + ) + if (listed.length) return listed + const tree = yield* input.git(["--git-dir", input.gitdir, "write-tree"]) + const hash = tree.text.trim() + if (tree.code !== 0 || !hash) return [] + if (!(yield* pin(input, hash))) return [] + return [hash] + }) + if (!roots.length) return false + + // Without --local, repack copies all reachable objects from alternates into this repository. + const packed = yield* input.git(["--git-dir", input.gitdir, "repack", "-a", "-d", "--no-write-bitmap-index"]) + if (packed.code !== 0) { + log.warn("failed to repack snapshot objects", { stderr: packed.stderr }) + return false + } + + const source = yield* Effect.gen(function* () { + const objects = text + .split("\n") + .map((item) => item.trim()) + .find(Boolean) + if (!objects || path.basename(objects) !== "objects") return + const gitdir = path.dirname(objects) + const name = ref(input.gitdir) + const result = yield* input.git(["--git-dir", gitdir, "rev-parse", "--verify", name]) + const hash = result.text.trim() + if (result.code !== 0 || !hash) return + return { gitdir, ref: name, hash } + }) + + const connected = yield* Effect.acquireUseRelease( + input.fs.rename(alt, hold), + () => + input.git([ + "--git-dir", + input.gitdir, + "fsck", + "--connectivity-only", + "--no-dangling", + "--no-reflogs", + "--no-progress", + ]), + () => input.fs.rename(hold, alt).pipe(Effect.orDie), + ) + + if (!(yield* input.fs.exists(alt))) return false + if (connected.code !== 0) { + log.warn("snapshot pack failed local connectivity check", { stderr: connected.stderr }) + return false + } + + if (source) { + const removed = yield* input.git(["--git-dir", source.gitdir, "update-ref", "-d", source.ref, source.hash]) + if (removed.code !== 0) { + log.warn("failed to remove source snapshot pin", { stderr: removed.stderr }) + return false + } + } + yield* Effect.uninterruptible( + Effect.gen(function* () { + yield* input.fs.remove(alt) + yield* input.fs + .remove(path.join(input.gitdir, "seed-objects"), { recursive: true }) + .pipe(Effect.catch(() => Effect.void)) + }), + ) + log.info("snapshot objects materialized", { roots: roots.length, duration: Date.now() - started }) + return true + }) +} diff --git a/packages/opencode/src/kilocode/snapshot/seed.ts b/packages/opencode/src/kilocode/snapshot/seed.ts index 4ca729d1c1..75665cc8e9 100644 --- a/packages/opencode/src/kilocode/snapshot/seed.ts +++ b/packages/opencode/src/kilocode/snapshot/seed.ts @@ -2,6 +2,7 @@ import { Effect } from "effect" import path from "path" import { AppFileSystem } from "@opencode-ai/core/filesystem" import * as Log from "@opencode-ai/core/util/log" +import { KiloSnapshotMaterialize } from "./materialize" export namespace KiloSnapshotSeed { const log = Log.create({ service: "snapshot.seed" }) @@ -26,11 +27,14 @@ export namespace KiloSnapshotSeed { readonly fs: AppFileSystem.Interface } + export interface Source { + readonly gitdir: string + readonly staging: string + readonly hash: string + } + export interface Output { - readonly seeded: boolean - readonly paths: number - readonly dropped: number - readonly reason?: string + readonly source?: Source } const list = (text: string) => text.split("\0").filter(Boolean) @@ -42,20 +46,36 @@ export namespace KiloSnapshotSeed { export const seed = Effect.fnUntraced(function* (input: Input) { const started = Date.now() const alt = path.join(input.gitdir, "objects", "info", "alternates") + const pending = `${alt}.seed` + const temp = path.join(input.gitdir, "seed.index") const changed = { value: false } + const pinned = { gitdir: "", ref: "", hash: "" } const reset = Effect.fnUntraced(function* (reason: string, warn = false) { + const retained = { value: false } + if (pinned.hash) { + const removed = yield* input.git(["--git-dir", pinned.gitdir, "update-ref", "-d", pinned.ref, pinned.hash]) + retained.value = removed.code !== 0 + } if (changed.value) { const cleared = yield* input.git(snap(input, ["read-tree", "--empty"]), { cwd: input.dir }) if (cleared.code !== 0) { yield* input.fs.remove(path.join(input.gitdir, "index")).pipe(Effect.catch(() => Effect.void)) } yield* input.fs.remove(path.join(input.gitdir, "index.lock")).pipe(Effect.catch(() => Effect.void)) - yield* input.fs.remove(alt).pipe(Effect.catch(() => Effect.void)) + yield* input.fs.remove(temp).pipe(Effect.catch(() => Effect.void)) + yield* input.fs.remove(`${temp}.lock`).pipe(Effect.catch(() => Effect.void)) + yield* input.fs.remove(pending).pipe(Effect.catch(() => Effect.void)) + if (!retained.value) { + yield* input.fs.remove(alt).pipe(Effect.catch(() => Effect.void)) + yield* input.fs + .remove(path.join(input.gitdir, "seed-objects"), { recursive: true }) + .pipe(Effect.catch(() => Effect.void)) + } } const fields = { reason, duration: Date.now() - started } if (warn) log.warn("snapshot seed failed; using cold initialization", fields) if (!warn) log.info("snapshot seed skipped", fields) - return { seeded: false, paths: 0, dropped: 0, reason } satisfies Output + return {} satisfies Output }) const attempt = Effect.gen(function* () { @@ -92,32 +112,52 @@ export namespace KiloSnapshotSeed { const objects = path.join(common, "objects") if (!(yield* input.fs.exists(objects))) return yield* reset("source-objects") - // Borrow committed objects to keep the first turn fast. Durable materialization can happen off this critical path. + const staging = path.join(input.gitdir, "seed-objects") + yield* input.fs.ensureDir(staging) + // Borrow committed objects while writing dirty content into snapshot-owned staging. yield* input.fs.ensureDir(path.dirname(alt)) changed.value = true - yield* input.fs.writeFileString(alt, `${objects}\n`) - - const tree = yield* input.git(["write-tree"], { - cwd: input.dir, - env: { - GIT_DIR: source, - GIT_WORK_TREE: input.worktree, - GIT_INDEX_FILE: index, - GIT_OBJECT_DIRECTORY: path.join(input.gitdir, "objects"), - GIT_ALTERNATE_OBJECT_DIRECTORIES: objects, - }, - }) - if (tree.code !== 0 || !tree.text.trim()) return yield* reset("write-tree", true) - - const read = yield* input.git(snap(input, ["read-tree", tree.text.trim()]), { cwd: input.dir }) + yield* input.fs.writeFileString(pending, `${objects}\n${staging}\n`) + yield* input.fs.rename(pending, alt) + + // Normalize a private index copy so write-tree cannot mutate the user's index + // and the snapshot does not retain source-local index extensions. + yield* input.fs.copyFile(index, temp) + const env = { + GIT_DIR: source, + GIT_WORK_TREE: input.worktree, + GIT_INDEX_FILE: temp, + } + const normalized = yield* input.git( + ["update-index", "--no-split-index", "--no-fsmonitor", "--no-untracked-cache"], + { cwd: input.dir, env }, + ) + if (normalized.code !== 0) return yield* reset("normalize-index", true) + + const tree = yield* input.git(["write-tree"], { cwd: input.dir, env }) + const hash = tree.text.trim() + if (tree.code !== 0 || !hash) return yield* reset("write-tree", true) + + const ref = KiloSnapshotMaterialize.ref(input.gitdir) + const pin = yield* input.git(["--git-dir", common, "update-ref", ref, hash]) + if (pin.code !== 0) return yield* reset("source-pin", true) + pinned.gitdir = common + pinned.ref = ref + pinned.hash = hash + const materialize = { gitdir: common, staging, hash } + + // Discard source stat data and entry flags so reconciliation observes the + // filesystem under snapshot filter and line-ending semantics. + const read = yield* input.git(snap(input, ["read-tree", hash]), { cwd: input.dir }) if (read.code !== 0) return yield* reset("read-tree", true) + yield* input.fs.remove(temp).pipe(Effect.catch(() => Effect.void)) const tracked = yield* input.git(snap(input, ["ls-files", "-z", "--", "."]), { cwd: input.dir }) if (tracked.code !== 0) return yield* reset("list", true) const files = list(tracked.text) if (!files.length) { log.info("snapshot seed complete", { paths: 0, dropped: 0, duration: Date.now() - started }) - return { seeded: true, paths: 0, dropped: 0 } satisfies Output + return { source: materialize } satisfies Output } const ignored = yield* input.git(["-C", input.worktree, "check-ignore", "--no-index", "--stdin", "-z"], { @@ -157,10 +197,11 @@ export namespace KiloSnapshotSeed { large: large.length, duration: Date.now() - started, }) - return { seeded: true, paths: files.length, dropped: dropped.length } satisfies Output + return { source: materialize } satisfies Output }) return yield* attempt.pipe( + Effect.onInterrupt(() => reset("interrupted", true).pipe(Effect.asVoid)), Effect.catch((err) => { log.warn("snapshot seed failed", { err }) return reset("error", true) diff --git a/packages/opencode/src/kilocode/snapshot/track.ts b/packages/opencode/src/kilocode/snapshot/track.ts index 269b5b94ca..6daad81d5b 100644 --- a/packages/opencode/src/kilocode/snapshot/track.ts +++ b/packages/opencode/src/kilocode/snapshot/track.ts @@ -556,7 +556,7 @@ export namespace KiloSnapshotTrack { * tests that bypass the runtime). */ async function currentDirectory(): Promise<{ directory: string; worktree?: string } | undefined> { - const { Instance } = await import("@/project/instance") + const { Instance } = await import("@/kilocode/instance") try { return { directory: Instance.directory, worktree: Instance.worktree } } catch { diff --git a/packages/opencode/src/kilocode/suggestion/index.ts b/packages/opencode/src/kilocode/suggestion/index.ts index 19e82469dc..28e4a14ae8 100644 --- a/packages/opencode/src/kilocode/suggestion/index.ts +++ b/packages/opencode/src/kilocode/suggestion/index.ts @@ -8,6 +8,7 @@ import { Telemetry } from "@kilocode/kilo-telemetry" import z from "zod" import { Schema } from "effect" import { KiloSessionPromptQueue } from "../session/prompt-queue" +import { Instance } from "../instance" import { parseReviewCommand } from "../review/command" export namespace Suggestion { @@ -167,7 +168,7 @@ export namespace Suggestion { actionCount: info.actions.length, }) }) - Bus.publish(Event.Shown, { ...info, sessionID: SessionID.make(info.sessionID) }) + Bus.publish(Instance.current, Event.Shown, { ...info, sessionID: SessionID.make(info.sessionID) }) }) } @@ -203,7 +204,7 @@ export namespace Suggestion { }) } - Bus.publish(Event.Accepted, { + Bus.publish(Instance.current, Event.Accepted, { sessionID: SessionID.make(existing.info.sessionID), requestID: existing.info.id, index: input.index, @@ -225,7 +226,7 @@ export namespace Suggestion { log.info("dismissed", { requestID }) - Bus.publish(Event.Dismissed, { + Bus.publish(Instance.current, Event.Dismissed, { sessionID: SessionID.make(existing.info.sessionID), requestID: existing.info.id, }) @@ -246,7 +247,7 @@ export namespace Suggestion { if (entry.info.sessionID !== sessionID) continue delete s.pending[id] log.info("dismissed", { requestID: id }) - Bus.publish(Event.Dismissed, { + Bus.publish(Instance.current, Event.Dismissed, { sessionID: SessionID.make(entry.info.sessionID), requestID: entry.info.id, }) diff --git a/packages/opencode/src/kilocode/system-prompt.ts b/packages/opencode/src/kilocode/system-prompt.ts index 0274377674..7c0f22c412 100644 --- a/packages/opencode/src/kilocode/system-prompt.ts +++ b/packages/opencode/src/kilocode/system-prompt.ts @@ -3,7 +3,7 @@ import { Global } from "@opencode-ai/core/global" import { staticEnvLines, type EditorContext } from "@/kilocode/editor-context" import type { Provider } from "@/provider/provider" -import type { InstanceContext } from "@/project/instance" +import type { InstanceContext } from "@/project/instance-context" export namespace KilocodeSystemPrompt { export function environment(input: { ctx: InstanceContext; model: Provider.Model; editor?: EditorContext }) { diff --git a/packages/opencode/src/kilocode/tool/semantic-search.ts b/packages/opencode/src/kilocode/tool/semantic-search.ts index 872f40ed9c..9bbbc85a3d 100644 --- a/packages/opencode/src/kilocode/tool/semantic-search.ts +++ b/packages/opencode/src/kilocode/tool/semantic-search.ts @@ -2,7 +2,7 @@ import { Effect, Schema } from "effect" import path from "path" import * as Tool from "@/tool/tool" import { KiloIndexing } from "@/kilocode/indexing" -import { Instance } from "@/project/instance" +import { Instance } from "@/kilocode/instance" import DESCRIPTION from "./semantic-search.txt" diff --git a/packages/opencode/src/kilocode/ts-client.ts b/packages/opencode/src/kilocode/ts-client.ts index 8046df0df4..cb2b3570dc 100644 --- a/packages/opencode/src/kilocode/ts-client.ts +++ b/packages/opencode/src/kilocode/ts-client.ts @@ -9,7 +9,7 @@ import { TsCheck } from "./ts-check" import * as Log from "@opencode-ai/core/util/log" import { withTimeout } from "../util/timeout" import path from "path" -import { Instance } from "../project/instance" +import { Instance } from "./instance" export namespace TsClient { const log = Log.create({ service: "ts-client" }) @@ -30,7 +30,7 @@ export namespace TsClient { diagnostics.set(file, diags) } for (const file of result.keys()) { - Bus.publish(LSPClient.Event.Diagnostics, { + Bus.publish(Instance.current, LSPClient.Event.Diagnostics, { path: file, serverID: client.serverID, }) diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index 30577a8f11..205cba6f29 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -7,10 +7,13 @@ import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types import * as Log from "@opencode-ai/core/util/log" import { Process } from "@/util/process" import { LANGUAGE_EXTENSIONS } from "./language" -import { Schema } from "effect" +import { Effect, Schema } from "effect" import type * as LSPServer from "./server" import { withTimeout } from "../util/timeout" import { Filesystem } from "@/util/filesystem" +import { InstanceRef } from "@/effect/instance-ref" +import { makeRuntime } from "@/effect/run-service" +import type { InstanceContext } from "@/project/instance-context" const DIAGNOSTICS_DEBOUNCE_MS = 150 const DIAGNOSTICS_DOCUMENT_WAIT_TIMEOUT_MS = 5_000 @@ -25,6 +28,7 @@ const FILE_CHANGE_CHANGED = 2 const TEXT_DOCUMENT_SYNC_INCREMENTAL = 2 const log = Log.create({ service: "lsp.client" }) +const busRuntime = makeRuntime(Bus.Service, Bus.layer) export type Info = NonNullable>> @@ -134,9 +138,16 @@ function shouldSeedDiagnosticsOnFirstPush(serverID: string) { return serverID === "typescript" } -export async function create(input: { serverID: string; server: LSPServer.Handle; root: string; directory: string }) { +export async function create(input: { + serverID: string + server: LSPServer.Handle + root: string + directory: string + instance: InstanceContext +}) { const logger = log.clone().tag("serverID", input.serverID) logger.info("starting client") + const instance = input.instance const connection = createMessageConnection( new StreamMessageReader(input.server.process.stdout as any), @@ -162,7 +173,11 @@ export async function create(input: { serverID: string; server: LSPServer.Handle dedupeDiagnostics([...(pushDiagnostics.get(filePath) ?? []), ...(pullDiagnostics.get(filePath) ?? [])]) const updatePushDiagnostics = (filePath: string, next: Diagnostic[]) => { pushDiagnostics.set(filePath, next) - Bus.publish(Event.Diagnostics, { path: filePath, serverID: input.serverID }) + void busRuntime.runPromise((svc) => + svc + .publish(Event.Diagnostics, { path: filePath, serverID: input.serverID }) + .pipe(Effect.provideService(InstanceRef, instance)), + ) } const updatePullDiagnostics = (filePath: string, next: Diagnostic[]) => { pullDiagnostics.set(filePath, next) @@ -510,10 +525,14 @@ export async function create(input: { serverID: string; server: LSPServer.Handle } timeoutTimer = setTimeout(() => finish(false), request.timeout) - unsub = Bus.subscribe(Event.Diagnostics, (event) => { - if (event.properties.path !== request.path || event.properties.serverID !== input.serverID) return - schedule() - }) + unsub = busRuntime.runSync((svc) => + svc + .subscribeCallback(Event.Diagnostics, (event) => { + if (event.properties.path !== request.path || event.properties.serverID !== input.serverID) return + schedule() + }) + .pipe(Effect.provideService(InstanceRef, instance)), + ) schedule() }) } diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index 29450f7e80..ae08ab50d4 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -238,6 +238,7 @@ export const layer = Layer.effect( server: handle, root, directory: ctx.directory, + instance: ctx, }).catch(async (err) => { s.broken.add(key) await Process.stop(handle.process) @@ -264,6 +265,7 @@ export const layer = Layer.effect( if (!root) continue if (s.broken.has(root + server.id)) continue + // kilocode_change start - use lightweight tsgo-based client when persistent LSP is not enabled if (server.id === "typescript" && !flags.experimentalLspTool) { const existing = s.clients.find((x) => x.root === root && x.serverID === server.id) if (existing) { @@ -273,9 +275,10 @@ export const layer = Layer.effect( const client = TsClient.create({ root }) s.clients.push(client) result.push(client) - Bus.publish(Event.Updated, {}) + await Bus.publish(ctx, Event.Updated, {}) continue } + // kilocode_change end const match = s.clients.find((x) => x.root === root && x.serverID === server.id) if (match) { @@ -304,7 +307,7 @@ export const layer = Layer.effect( if (!client) continue result.push(client) - Bus.publish(Event.Updated, {}) + await Bus.publish(ctx, Event.Updated, {}) } return result diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index ba4344026c..61e4391c80 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -6,7 +6,7 @@ import * as Log from "@opencode-ai/core/util/log" import { text } from "node:stream/consumers" import fs from "fs/promises" import { Filesystem } from "@/util/filesystem" -import type { InstanceContext } from "../project/instance" +import type { InstanceContext } from "../project/instance-context" import { Flag } from "@opencode-ai/core/flag/flag" import { Archive } from "@/util/archive" import { Process } from "@/util/process" @@ -124,11 +124,11 @@ export const Vue: Info = { id: "vue", extensions: [".vue"], root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let binary = which("vue-language-server") const args: string[] = [] if (!binary) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return const resolved = await Npm.which("@vue/language-server") if (!resolved) return binary = resolved @@ -153,13 +153,13 @@ export const ESLint: Info = { id: "eslint", root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue"], - async spawn(root, ctx) { + async spawn(root, ctx, flags) { const eslint = Module.resolve("eslint", ctx.directory) if (!eslint) return log.info("spawning eslint server") const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js") if (!(await Filesystem.exists(serverPath))) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("downloading and building VS Code ESLint server") const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip") if (!response.ok) return @@ -349,11 +349,11 @@ export const Gopls: Info = { return NearestRoot(["go.mod", "go.sum"])(file, ctx) }, extensions: [".go"], - async spawn(root) { + async spawn(root, _ctx, flags) { let bin = which("gopls") if (!bin) { if (!which("go")) return - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("installing gopls") const proc = Process.spawn(["go", "install", "golang.org/x/tools/gopls@latest"], { @@ -384,7 +384,7 @@ export const Rubocop: Info = { id: "ruby-lsp", root: NearestRoot(["Gemfile"]), extensions: [".rb", ".rake", ".gemspec", ".ru"], - async spawn(root) { + async spawn(root, _ctx, flags) { let bin = which("rubocop") if (!bin) { const ruby = which("ruby") @@ -393,7 +393,7 @@ export const Rubocop: Info = { log.info("Ruby not found, please install Ruby first") return } - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("installing rubocop") const proc = Process.spawn(["gem", "install", "rubocop", "--bindir", Global.Path.bin], { stdout: "pipe", @@ -484,11 +484,11 @@ export const Pyright: Info = { id: "pyright", extensions: [".py", ".pyi"], root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let binary = which("pyright-langserver") const args = [] if (!binary) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return const resolved = await Npm.which("pyright", "pyright-langserver") if (!resolved) return binary = resolved @@ -528,7 +528,7 @@ export const ElixirLS: Info = { id: "elixir-ls", extensions: [".ex", ".exs"], root: NearestRoot(["mix.exs", "mix.lock"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let binary = which("elixir-ls") if (!binary) { const elixirLsPath = path.join(Global.Path.bin, "elixir-ls") @@ -546,7 +546,7 @@ export const ElixirLS: Info = { return } - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("downloading elixir-ls from GitHub releases") const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip") @@ -591,7 +591,7 @@ export const Zls: Info = { id: "zls", extensions: [".zig", ".zon"], root: NearestRoot(["build.zig"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let bin = which("zls") if (!bin) { @@ -601,7 +601,7 @@ export const Zls: Info = { return } - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("downloading zls from GitHub releases") const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest") @@ -703,8 +703,8 @@ export const CSharp: Info = { id: "csharp", root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]), extensions: [".cs", ".csx"], - async spawn(root) { - const bin = await getRoslynLanguageServer() + async spawn(root, _ctx, flags) { + const bin = await getRoslynLanguageServer(flags.disableLspDownload) if (!bin) return return { @@ -719,8 +719,8 @@ export const Razor: Info = { id: "razor", root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]), extensions: [".razor", ".cshtml"], - async spawn(root) { - const bin = await getRoslynLanguageServer() + async spawn(root, _ctx, flags) { + const bin = await getRoslynLanguageServer(flags.disableLspDownload) if (!bin) return const razor = await findVscodeRazorExtension() @@ -751,26 +751,26 @@ export const Razor: Info = { let roslynLanguageServerInstall: Promise | undefined -async function getRoslynLanguageServer() { +async function getRoslynLanguageServer(disableLspDownload: boolean) { const existing = which("roslyn-language-server") if (existing) return existing const global = await roslynLanguageServerGlobalPath() if (global) return global - roslynLanguageServerInstall ||= installRoslynLanguageServer().finally(() => { + roslynLanguageServerInstall ||= installRoslynLanguageServer(disableLspDownload).finally(() => { roslynLanguageServerInstall = undefined }) return roslynLanguageServerInstall } -async function installRoslynLanguageServer() { +async function installRoslynLanguageServer(disableLspDownload: boolean) { if (!which("dotnet")) { log.error(".NET SDK is required to install roslyn-language-server") return } - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (disableLspDownload) return log.info("installing roslyn-language-server via dotnet tool") const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], { stdout: "pipe", @@ -848,7 +848,7 @@ export const FSharp: Info = { id: "fsharp", root: NearestRoot([".slnx", ".sln", ".fsproj", "global.json"]), extensions: [".fs", ".fsi", ".fsx", ".fsscript"], - async spawn(root) { + async spawn(root, _ctx, flags) { let bin = which("fsautocomplete") if (!bin) { if (!which("dotnet")) { @@ -856,7 +856,7 @@ export const FSharp: Info = { return } - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("installing fsautocomplete via dotnet tool") const proc = Process.spawn(["dotnet", "tool", "install", "fsautocomplete", "--tool-path", Global.Path.bin], { stdout: "pipe", @@ -965,7 +965,7 @@ export const Clangd: Info = { id: "clangd", root: NearestRoot(["compile_commands.json", "compile_flags.txt", ".clangd"]), extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"], - async spawn(root) { + async spawn(root, _ctx, flags) { const args = ["--background-index", "--clang-tidy"] const fromPath = which("clangd") if (fromPath) { @@ -1000,7 +1000,7 @@ export const Clangd: Info = { } } - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("downloading clangd from GitHub releases") const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest") @@ -1015,10 +1015,12 @@ export const Clangd: Info = { } = await releaseResponse.json() const tag = release.tag_name - if (!tag) { + // kilocode_change start - reject release metadata before it becomes an executable path + if (!tag || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(tag)) { log.error("clangd release did not include a tag name") return } + // kilocode_change end const platform = process.platform const tokens: Record = { darwin: "mac", @@ -1055,7 +1057,9 @@ export const Clangd: Info = { return } - const archive = path.join(Global.Path.bin, name) + // kilocode_change start - do not use remote metadata as a local path + const archive = path.join(Global.Path.bin, name.endsWith(".zip") ? "clangd.zip" : "clangd.tar.xz") + // kilocode_change end const buf = await downloadResponse.arrayBuffer() if (buf.byteLength === 0) { log.error("Failed to write clangd archive") @@ -1111,11 +1115,11 @@ export const Svelte: Info = { id: "svelte", extensions: [".svelte"], root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let binary = which("svelteserver") const args: string[] = [] if (!binary) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return const resolved = await Npm.which("svelte-language-server") if (!resolved) return binary = resolved @@ -1138,7 +1142,7 @@ export const Astro: Info = { id: "astro", extensions: [".astro"], root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), - async spawn(root, ctx) { + async spawn(root, ctx, flags) { const tsserver = Module.resolve("typescript/lib/tsserver.js", ctx.directory) if (!tsserver) { log.info("typescript not found, required for Astro language server") @@ -1149,7 +1153,7 @@ export const Astro: Info = { let binary = which("astro-ls") const args: string[] = [] if (!binary) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return const resolved = await Npm.which("@astrojs/language-server") if (!resolved) return binary = resolved @@ -1199,7 +1203,7 @@ export const JDTLS: Info = { if (settingsRoot) return settingsRoot }, extensions: [".java"], - async spawn(root) { + async spawn(root, _ctx, flags) { const java = which("java") if (!java) { log.error("Java 21 or newer is required to run the JDTLS. Please install it first.") @@ -1217,7 +1221,7 @@ export const JDTLS: Info = { const launcherDir = path.join(distPath, "plugins") const installed = await pathExists(launcherDir) if (!installed) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("Downloading JDTLS LSP server.") await fs.mkdir(distPath, { recursive: true }) const releaseURL = @@ -1310,13 +1314,13 @@ export const KotlinLS: Info = { // 4) Maven fallback return NearestRoot(["pom.xml"])(file, ctx) }, - async spawn(root) { + async spawn(root, _ctx, flags) { const distPath = path.join(Global.Path.bin, "kotlin-ls") const launcherScript = process.platform === "win32" ? path.join(distPath, "kotlin-lsp.cmd") : path.join(distPath, "kotlin-lsp.sh") const installed = await Filesystem.exists(launcherScript) if (!installed) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("Downloading Kotlin Language Server from GitHub.") const releaseResponse = await fetch("https://api.github.com/repos/Kotlin/kotlin-lsp/releases/latest") @@ -1397,11 +1401,11 @@ export const YamlLS: Info = { id: "yaml-ls", extensions: [".yaml", ".yml"], root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let binary = which("yaml-language-server") const args: string[] = [] if (!binary) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return const resolved = await Npm.which("yaml-language-server") if (!resolved) return binary = resolved @@ -1431,11 +1435,11 @@ export const LuaLS: Info = { "selene.yml", ]), extensions: [".lua"], - async spawn(root) { + async spawn(root, _ctx, flags) { let bin = which("lua-language-server") if (!bin) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("downloading lua-language-server from GitHub releases") const releaseResponse = await fetch("https://api.github.com/repos/LuaLS/lua-language-server/releases/latest") @@ -1492,7 +1496,9 @@ export const LuaLS: Info = { return } - const tempPath = path.join(Global.Path.bin, assetName) + // kilocode_change start - use a fixed local archive name + const tempPath = path.join(Global.Path.bin, `lua-language-server.${ext}`) + // kilocode_change end if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body) // Unlike zls which is a single self-contained binary, @@ -1564,11 +1570,11 @@ export const PHPIntelephense: Info = { id: "php intelephense", extensions: [".php"], root: NearestRoot(["composer.json", "composer.lock", ".php-version"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let binary = which("intelephense") const args: string[] = [] if (!binary) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return const resolved = await Npm.which("intelephense") if (!resolved) return binary = resolved @@ -1648,11 +1654,11 @@ export const BashLS: Info = { id: "bash", extensions: [".sh", ".bash", ".zsh", ".ksh"], root: async (_file, ctx) => ctx.directory, - async spawn(root) { + async spawn(root, _ctx, flags) { let binary = which("bash-language-server") const args: string[] = [] if (!binary) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return const resolved = await Npm.which("bash-language-server") if (!resolved) return binary = resolved @@ -1674,11 +1680,11 @@ export const TerraformLS: Info = { id: "terraform", extensions: [".tf", ".tfvars"], root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let bin = which("terraform-ls") if (!bin) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("downloading terraform-ls from HashiCorp releases") const releaseResponse = await fetch("https://api.releases.hashicorp.com/v1/releases/terraform-ls/latest") @@ -1755,11 +1761,11 @@ export const TexLab: Info = { id: "texlab", extensions: [".tex", ".bib"], root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let bin = which("texlab") if (!bin) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("downloading texlab from GitHub releases") const response = await fetch("https://api.github.com/repos/latex-lsp/texlab/releases/latest") @@ -1843,11 +1849,11 @@ export const DockerfileLS: Info = { id: "dockerfile", extensions: [".dockerfile", "Dockerfile"], root: async (_file, ctx) => ctx.directory, - async spawn(root) { + async spawn(root, _ctx, flags) { let binary = which("docker-langserver") const args: string[] = [] if (!binary) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return const resolved = await Npm.which("dockerfile-language-server-nodejs") if (!resolved) return binary = resolved @@ -1939,11 +1945,11 @@ export const Tinymist: Info = { id: "tinymist", extensions: [".typ", ".typc"], root: NearestRoot(["typst.toml"]), - async spawn(root) { + async spawn(root, _ctx, flags) { let bin = which("tinymist") if (!bin) { - if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return + if (flags.disableLspDownload) return log.info("downloading tinymist from GitHub releases") const response = await fetch("https://api.github.com/repos/Myriad-Dreamin/tinymist/releases/latest") diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 641b496c72..209b5372b6 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -500,14 +500,7 @@ export function disabled(tools: string[], ruleset: Ruleset): Set { export const defaultLayer = layer.pipe(Layer.provide(Bus.layer), Layer.provide(Config.defaultLayer)) // kilocode_change // kilocode_change start — inverse of fromConfig: convert rules back to config format -const SCALAR_ONLY_PERMISSIONS = new Set([ - "todowrite", - "todoread", - "question", - "webfetch", - "websearch", - "doom_loop", -]) +const SCALAR_ONLY_PERMISSIONS = new Set(["todowrite", "todoread", "question", "webfetch", "websearch", "doom_loop"]) export function toConfig(rules: Ruleset): ConfigPermission.Info { const result: ConfigPermission.Info = {} diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index c40e6bbd90..a4adcc894d 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -18,7 +18,7 @@ import { gitlabAuthPlugin as GitlabAuthPlugin } from "opencode-gitlab-auth" import { PoeAuthPlugin } from "opencode-poe-auth" import { CloudflareAIGatewayAuthPlugin, CloudflareWorkersAuthPlugin } from "./cloudflare" import { AzureAuthPlugin } from "./azure" -import { XaiAuthPlugin } from "./xai" // kilocode_change +import { XaiAuthPlugin } from "./xai" import { DigitalOceanAuthPlugin } from "./digitalocean" import { Effect, Layer, Context, Stream } from "effect" import { EffectBridge } from "@/effect/bridge" @@ -26,7 +26,7 @@ import { InstanceState } from "@/effect/instance-state" import { errorMessage } from "@/util/error" import { PluginLoader } from "./loader" import { parsePluginSpecifier, readPluginId, readV1Plugin, resolvePluginId } from "./shared" -import { KiloAuthPlugin } from "@kilocode/kilo-gateway" // kilocode_change +import { KiloAuthPlugin } from "@kilocode/kilo-gateway" import { CzCodeLakehousePlugin } from "@czcode/lakehouse" // czcode_change import { registerAdapter } from "@/control-plane/adapters" import type { WorkspaceAdapter } from "@/control-plane/types" @@ -60,12 +60,10 @@ export interface Interface { export class Service extends Context.Service()("@opencode/Plugin") {} // Built-in plugins that are directly imported (not installed from npm) -// kilocode_change start const INTERNAL_PLUGINS: PluginInstance[] = [ KiloAuthPlugin, CodexAuthPlugin, CopilotAuthPlugin, - // kilocode_change - external auth plugins ship against @opencode-ai/plugin; bridge to our @kilocode/plugin types GitlabAuthPlugin as unknown as PluginInstance, PoeAuthPlugin as unknown as PluginInstance, CloudflareWorkersAuthPlugin, @@ -75,7 +73,6 @@ const INTERNAL_PLUGINS: PluginInstance[] = [ DigitalOceanAuthPlugin, // czcode_change CzCodeLakehousePlugin as unknown as PluginInstance, // czcode_change ] -// kilocode_change end function isServerPlugin(value: unknown): value is PluginInstance { return typeof value === "function" @@ -96,8 +93,7 @@ function getLegacyPlugins(mod: Record) { if (seen.has(entry)) continue seen.add(entry) const plugin = getServerPlugin(entry) - // kilocode_change: skip named exports (e.g. constants from @kilocode/plugin-atomic-chat) - if (!plugin) continue // kilocode_change + if (!plugin) continue result.push(plugin) } diff --git a/packages/opencode/src/project/instance-store.ts b/packages/opencode/src/project/instance-store.ts index eb65772dae..a69e5e1f67 100644 --- a/packages/opencode/src/project/instance-store.ts +++ b/packages/opencode/src/project/instance-store.ts @@ -4,7 +4,7 @@ import { InstanceRef } from "@/effect/instance-ref" import { disposeInstance as runDisposers } from "@/effect/instance-registry" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Context, Deferred, Duration, Effect, Exit, Layer, Scope } from "effect" -import { context as instanceContext, type InstanceContext } from "./instance-context" +import { context as instanceContext, type InstanceContext } from "./instance-context" // kilocode_change import { InstanceBootstrap } from "./bootstrap-service" import * as Project from "./project" @@ -52,10 +52,11 @@ export const layer: Layer.Layer yield* Effect.promise(() => instanceContext.provide(ctx, () => Effect.runPromise(ready))) + // kilocode_change end return ctx }).pipe(Effect.withSpan("InstanceStore.boot")) @@ -90,7 +91,7 @@ export const layer: Layer.Layer runDisposers(ctx.directory)) + yield* Effect.promise(() => instanceContext.provide(ctx, () => runDisposers(ctx.directory))) // kilocode_change yield* emitDisposed({ directory: ctx.directory, project: ctx.project.id }) }) @@ -130,8 +131,14 @@ export const layer: Layer.Layer runDisposers(directory)) + // kilocode_change start - dispose reloads under the previous instance context + const exit = yield* Deferred.await(previous.deferred).pipe(Effect.exit) + yield* Effect.promise(() => + Exit.isSuccess(exit) + ? instanceContext.provide(exit.value, () => runDisposers(directory)) + : runDisposers(directory), + ) + // kilocode_change end yield* emitDisposed({ directory, project: input.project?.id }) } yield* completeLoad(directory, input, entry) diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts deleted file mode 100644 index a54291cf0c..0000000000 --- a/packages/opencode/src/project/instance.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { context, type InstanceContext } from "./instance-context" - -export type { InstanceContext } from "./instance-context" - -export const Instance = { - get current() { - return context.use() - }, - get directory() { - return context.use().directory - }, - get worktree() { - return context.use().worktree - }, - get project() { - return context.use().project - }, - - /** - * Captures the current instance ALS context and returns a wrapper that - * restores it when called. Use this for callbacks that fire outside the - * instance async context (native addons, event emitters, timers, etc.). - */ - bind any>(fn: F): F { - const ctx = context.use() - return ((...args: any[]) => context.provide(ctx, () => fn(...args))) as F - }, - /** - * Run a synchronous function within the given instance context ALS. - * Use this to bridge from Effect (where InstanceRef carries context) - * back to sync code that reads Instance.directory from ALS. - */ - restore(ctx: InstanceContext, fn: () => R): R { - return context.provide(ctx, fn) - }, -} diff --git a/packages/opencode/src/project/with-instance.ts b/packages/opencode/src/project/with-instance.ts deleted file mode 100644 index b7b5360c75..0000000000 --- a/packages/opencode/src/project/with-instance.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AppRuntime } from "@/effect/app-runtime" -import { context } from "./instance-context" -import { InstanceStore } from "./instance-store" - -export async function provide(input: { directory: string; fn: () => R }): Promise { - const ctx = await AppRuntime.runPromise( - InstanceStore.Service.use((store) => store.load({ directory: input.directory })), - ) - return context.provide(ctx, () => input.fn()) -} - -export * as WithInstance from "./with-instance" diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index b5c0bb1ebf..f0bb61ba1e 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -1,4 +1,4 @@ -// kilocode_change - adapt Kilo model assembly to the upstream core models service +// kilocode_change - new file import { Config } from "@/config/config" import { Auth } from "@/auth" import { ModelCache } from "./model-cache" @@ -30,74 +30,72 @@ function baseURL(url: string | undefined, org: string | undefined) { return `${base}/api/openrouter` } -export const layer: Layer.Layer< - Service, - never, - Core.Service | Config.Service | Auth.Service | ModelCache.Service -> = Layer.effect( - Service, - Effect.gen(function* () { - const core = yield* Core.Service - const config = yield* Config.Service - const auth = yield* Auth.Service - const cache = yield* ModelCache.Service +export const layer: Layer.Layer = + Layer.effect( + Service, + Effect.gen(function* () { + const core = yield* Core.Service + const config = yield* Config.Service + const auth = yield* Auth.Service + const cache = yield* ModelCache.Service - const get = Effect.fn("ModelsDev.get")(function* () { - const providers = { ...(yield* core.get()) } - delete providers.kilo + const get = Effect.fn("ModelsDev.get")(function* () { + const providers = { ...(yield* core.get()) } + delete providers.kilo - const cfg = yield* config.get() - const disabled = new Set(cfg.disabled_providers ?? []) - const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : undefined - const allowed = (!enabled || enabled.has("kilo")) && !disabled.has("kilo") - const apt = cfg.provider?.apertis?.options - const aptURL = apt?.baseURL ?? "https://api.apertis.ai/v1" - const aptOpts = apt?.baseURL ? { baseURL: apt.baseURL } : {} + const cfg = yield* config.get() + const disabled = new Set(cfg.disabled_providers ?? []) + const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : undefined + const allowed = (!enabled || enabled.has("kilo")) && !disabled.has("kilo") + const apt = cfg.provider?.apertis?.options + const aptURL = apt?.baseURL ?? "https://api.apertis.ai/v1" + const aptOpts = apt?.baseURL ? { baseURL: apt.baseURL } : {} - const addApertis = Effect.fnUntraced(function* () { - if (providers.apertis) return - const models = yield* cache.fetch("apertis", aptOpts).pipe(Effect.catch(() => Effect.succeed({}))) - providers.apertis = { - id: "apertis", - name: "Apertis", - env: ["APERTIS_API_KEY"], - api: aptURL, - npm: "@ai-sdk/openai-compatible", - models, + const addApertis = Effect.fnUntraced(function* () { + if (providers.apertis) return + const models = yield* cache.fetch("apertis", aptOpts).pipe(Effect.catch(() => Effect.succeed({}))) + providers.apertis = { + id: "apertis", + name: "Apertis", + env: ["APERTIS_API_KEY"], + api: aptURL, + npm: "@ai-sdk/openai-compatible", + models, + } + if (Object.keys(models).length === 0) + yield* cache.refresh("apertis", aptOpts).pipe(Effect.ignore, Effect.forkDetach) + }) + + if (!allowed) { + yield* addApertis() + return providers } - if (Object.keys(models).length === 0) yield* cache.refresh("apertis", aptOpts).pipe(Effect.ignore, Effect.forkDetach) - }) - if (!allowed) { + const opts = cfg.provider?.kilo?.options + const info = yield* auth.get("kilo").pipe(Effect.catch(() => Effect.succeed(undefined))) + const org = opts?.kilocodeOrganizationId ?? (info?.type === "oauth" ? info.accountId : undefined) + const url = baseURL(opts?.baseURL, org) + const fetch = { + ...(url ? { baseURL: url } : {}), + ...(org ? { kilocodeOrganizationId: org } : {}), + } + const models = yield* cache.fetch("kilo", fetch).pipe(Effect.catch(() => Effect.succeed({}))) + providers.kilo = { + id: "kilo", + name: "Kilo Gateway", + env: ["KILO_API_KEY"], + api: KILO_OPENROUTER_BASE.endsWith("/") ? KILO_OPENROUTER_BASE : `${KILO_OPENROUTER_BASE}/`, + npm: "@kilocode/kilo-gateway", + models, + } + if (Object.keys(models).length === 0) yield* cache.refresh("kilo", fetch).pipe(Effect.ignore, Effect.forkDetach) yield* addApertis() return providers - } - - const opts = cfg.provider?.kilo?.options - const info = yield* auth.get("kilo").pipe(Effect.catch(() => Effect.succeed(undefined))) - const org = opts?.kilocodeOrganizationId ?? (info?.type === "oauth" ? info.accountId : undefined) - const url = baseURL(opts?.baseURL, org) - const fetch = { - ...(url ? { baseURL: url } : {}), - ...(org ? { kilocodeOrganizationId: org } : {}), - } - const models = yield* cache.fetch("kilo", fetch).pipe(Effect.catch(() => Effect.succeed({}))) - providers.kilo = { - id: "kilo", - name: "Kilo Gateway", - env: ["KILO_API_KEY"], - api: KILO_OPENROUTER_BASE.endsWith("/") ? KILO_OPENROUTER_BASE : `${KILO_OPENROUTER_BASE}/`, - npm: "@kilocode/kilo-gateway", - models, - } - if (Object.keys(models).length === 0) yield* cache.refresh("kilo", fetch).pipe(Effect.ignore, Effect.forkDetach) - yield* addApertis() - return providers - }) + }) - return Service.of({ get, refresh: core.refresh }) - }), -) + return Service.of({ get, refresh: core.refresh }) + }), + ) export const defaultLayer = layer.pipe( Layer.provide(Core.defaultLayer), diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index fac770091a..96966de484 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -4,7 +4,6 @@ import type { JSONSchema7 } from "@ai-sdk/provider" import type * as Provider from "./provider" import type * as ModelsDev from "@opencode-ai/core/models" import { iife } from "@/util/iife" -import { Flag } from "@opencode-ai/core/flag/flag" import { kiloProviderOptions } from "@/kilocode/provider-options" import { isLing } from "@/kilocode/model-match" @@ -18,7 +17,7 @@ function mimeToModality(mime: string): Modality | undefined { return undefined } -export const OUTPUT_TOKEN_MAX = Flag.KILO_EXPERIMENTAL_OUTPUT_TOKEN_MAX || 32_000 +export const OUTPUT_TOKEN_MAX = 32_000 export function sanitizeSurrogates(content: string) { return content.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(? { + const parameters = operation.parameters + if (!Array.isArray(parameters)) return operation + return { + ...operation, + parameters: parameters.map((parameter) => + parameter?.name === "location" && parameter?.in === "query" + ? { ...parameter, style: "deepObject", explode: true } + : parameter, + ), + } + }, +}) + +export class V2LocationMiddleware extends HttpApiMiddleware.Service< + V2LocationMiddleware, + { + provides: Catalog.Service | PluginBoot.Service + } +>()("@opencode/ExperimentalHttpApiV2Location") {} + +function ref(request: HttpServerRequest.HttpServerRequest): Location.Ref { + const query = new URL(request.url, "http://localhost").searchParams + return { + directory: query.get("location[directory]") || request.headers["x-kilo-directory"] || process.cwd(), + workspaceID: query.get("location[workspace]") || request.headers["x-kilo-workspace"], + } +} + +export const layer = Layer.effect( + V2LocationMiddleware, + Effect.gen(function* () { + const locations = yield* LocationServiceMap + return V2LocationMiddleware.of((effect) => + Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest + return yield* effect.pipe(Effect.provide(locations.get(ref(request)))) + }), + ) + }), +).pipe(Layer.provide(LocationServiceMap.layer)) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/message.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/message.ts index 060c6c8a83..131a142586 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/message.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/message.ts @@ -1,5 +1,5 @@ import { SessionID } from "@/session/schema" -import { SessionMessage } from "@/v2/session-message" +import { SessionMessage } from "@opencode-ai/core/session-message" import { Schema } from "effect" import { HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "../../middleware/authorization" diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts index 47b8ec8965..d265ac7fc2 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts @@ -2,15 +2,15 @@ import { ModelV2 } from "@opencode-ai/core/model" import { Schema } from "effect" import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "../../middleware/authorization" -import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance" +import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location" export const ModelGroup = HttpApiGroup.make("v2.model") .add( HttpApiEndpoint.get("models", "/api/model", { - query: InstanceQuery, + query: LocationQuery, success: Schema.Array(ModelV2.Info), }) - .annotateMerge(instanceQueryOpenApi) + .annotateMerge(locationQueryOpenApi) .annotateMerge( OpenApi.annotations({ identifier: "v2.model.list", @@ -25,5 +25,5 @@ export const ModelGroup = HttpApiGroup.make("v2.model") description: "Experimental v2 model routes.", }), ) - .middleware(V2InstanceMiddleware) + .middleware(V2LocationMiddleware) .middleware(Authorization) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts index e62cfc3240..7a482ce114 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts @@ -3,15 +3,15 @@ import { Schema } from "effect" import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { ApiNotFoundError } from "../../errors" import { Authorization } from "../../middleware/authorization" -import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance" +import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location" export const ProviderGroup = HttpApiGroup.make("v2.provider") .add( HttpApiEndpoint.get("providers", "/api/provider", { - query: InstanceQuery, + query: LocationQuery, success: Schema.Array(ProviderV2.Info), }) - .annotateMerge(instanceQueryOpenApi) + .annotateMerge(locationQueryOpenApi) .annotateMerge( OpenApi.annotations({ identifier: "v2.provider.list", @@ -23,11 +23,11 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider") .add( HttpApiEndpoint.get("provider", "/api/provider/:providerID", { params: { providerID: ProviderV2.ID }, - query: InstanceQuery, + query: LocationQuery, success: ProviderV2.Info, error: ApiNotFoundError, }) - .annotateMerge(instanceQueryOpenApi) + .annotateMerge(locationQueryOpenApi) .annotateMerge( OpenApi.annotations({ identifier: "v2.provider.get", @@ -43,5 +43,5 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider") description: "Experimental v2 provider routes.", }), ) - .middleware(V2InstanceMiddleware) + .middleware(V2LocationMiddleware) .middleware(Authorization) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts index 3776f5c72a..0313b5c097 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts @@ -1,5 +1,5 @@ import { SessionID } from "@/session/schema" -import { SessionMessage } from "@/v2/session-message" +import { SessionMessage } from "@opencode-ai/core/session-message" import { Prompt } from "@opencode-ai/core/session-prompt" import { SessionV2 } from "@/v2/session" import { Schema } from "effect" diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts index 7739001db7..daa799b7a8 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts @@ -1,12 +1,12 @@ import { SessionV2 } from "@/v2/session" import { Layer } from "effect" -import { layer as v2InstanceLayer } from "../groups/v2/instance" +import { layer as v2LocationLayer } from "../groups/v2/location" import { messageHandlers } from "./v2/message" import { modelHandlers } from "./v2/model" import { providerHandlers } from "./v2/provider" import { sessionHandlers } from "./v2/session" export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers, modelHandlers, providerHandlers).pipe( - Layer.provide(v2InstanceLayer), + Layer.provide(v2LocationLayer), Layer.provide(SessionV2.defaultLayer), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/message.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/message.ts index 92e37142b4..fd710ba954 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/message.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/message.ts @@ -1,4 +1,4 @@ -import { SessionMessage } from "@/v2/session-message" +import { SessionMessage } from "@opencode-ai/core/session-message" import { SessionV2 } from "@/v2/session" import { Effect, Schema } from "effect" import * as DateTime from "effect/DateTime" diff --git a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts index 4edfa80787..30347d85ff 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts @@ -1,5 +1,5 @@ import { EffectBridge } from "@/effect/bridge" -import type { InstanceContext } from "@/project/instance" +import type { InstanceContext } from "@/project/instance-context" import { InstanceStore } from "@/project/instance-store" import * as Log from "@opencode-ai/core/util/log" import { Effect } from "effect" diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index 74c690ad6c..0ddb3c4e81 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -1,5 +1,7 @@ + import { NamedError } from "@opencode-ai/core/util/error" import * as Log from "@opencode-ai/core/util/log" +import { ConfigError } from "@/config/error" import { Cause, Effect } from "effect" import { HttpRouter, HttpServerError, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http" @@ -18,6 +20,13 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) if (!defect) return Effect.failCause(cause) const error = defect.defect + if ( + error instanceof NamedError && + (ConfigError.InvalidError.isInstance(error) || ConfigError.JsonError.isInstance(error)) + ) { + return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 400 })) + } + log.error("failed", { error, cause: Cause.pretty(cause) }) return Effect.succeed( diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/instance-context.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/instance-context.ts index d4913696d2..90f3ce4773 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/instance-context.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/instance-context.ts @@ -1,4 +1,4 @@ -import { WorkspaceRef } from "@/effect/instance-ref" +import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref" import { InstanceStore } from "@/project/instance-store" import { Effect, Layer } from "effect" import { HttpRouter, HttpServerResponse } from "effect/unstable/http" @@ -26,9 +26,10 @@ function provideInstanceContext( ): Effect.Effect { return Effect.gen(function* () { const route = yield* WorkspaceRouteContext - return yield* store.provide( - { directory: decode(route.directory) }, - effect.pipe(Effect.provideService(WorkspaceRef, route.workspaceID)), + const ctx = yield* store.load({ directory: decode(route.directory) }) + return yield* effect.pipe( + Effect.provideService(InstanceRef, ctx), + Effect.provideService(WorkspaceRef, route.workspaceID), ) }) } diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts index 0343ccfc87..018e1f261f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts @@ -1,8 +1,7 @@ -import { getAdapter } from "@/control-plane/adapters" import { WorkspaceID } from "@/control-plane/schema" import type { Target } from "@/control-plane/types" import { Workspace } from "@/control-plane/workspace" -import { EffectBridge } from "@/effect/bridge" +import { WorkspaceAdapterRuntime } from "@/control-plane/workspace-adapter-runtime" import { Session } from "@/session/session" import { HttpApiProxy } from "./proxy" import * as Fence from "@/server/shared/fence" @@ -93,8 +92,7 @@ function missingWorkspaceResponse(id: WorkspaceID): HttpServerResponse.HttpServe } function resolveTarget(workspace: Workspace.Info): Effect.Effect { - const adapter = getAdapter(workspace.projectID, workspace.type) - return EffectBridge.fromPromise(() => adapter.target(workspace)) + return WorkspaceAdapterRuntime.target(workspace) } function proxyRemote( diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index e06d7d2a8a..5f426cf138 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -47,6 +47,7 @@ import { SessionSummary } from "@/session/summary" import { Todo } from "@/session/todo" import { SessionShare } from "@/share/session" import { ShareNext } from "@/share/share-next" +import { EventV2Bridge } from "@/event-v2-bridge" import { Skill } from "@/skill" import { Snapshot } from "@/snapshot" import { Storage } from "@/storage/storage" // kilocode_change @@ -112,6 +113,7 @@ const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provi const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)) const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe( Layer.provide([controlHandlers, globalHandlers]), + Layer.provide(schemaErrorLayer), Layer.provide(httpApiAuthLayer), ) const instanceRouterLayer = authorizationRouterMiddleware @@ -149,6 +151,7 @@ const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe httpApiAuthLayer, workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)), instanceContextLayer, + schemaErrorLayer, ]), ) @@ -185,7 +188,6 @@ export function createRoutes( corsOptions?: CorsOptions, ): Layer.Layer { return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, docRoute, uiRoute).pipe( - Layer.provide(schemaErrorLayer), // kilocode_change Layer.provide([ errorLayer, compressionLayer, @@ -228,6 +230,7 @@ export function createRoutes( Snapshot.defaultLayer, Storage.defaultLayer, // kilocode_change SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, Skill.defaultLayer, Todo.defaultLayer, ToolRegistry.defaultLayer, diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index b7a46918b2..6f503b65df 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -1,3 +1,4 @@ + import { NodeHttpServer } from "@effect/platform-node" import * as Log from "@opencode-ai/core/util/log" import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect" @@ -5,7 +6,7 @@ import { HttpRouter, HttpServer } from "effect/unstable/http" import { OpenApi } from "effect/unstable/httpapi" import { createServer } from "node:http" import { MDNS } from "./mdns" -import { initProjectors } from "./projectors" +import "./init-projectors" import { HttpApiApp } from "./routes/instance/httpapi/server" import { disposeMiddleware } from "./routes/instance/httpapi/lifecycle" import { WebSocketTracker } from "./routes/instance/httpapi/websocket-tracker" @@ -16,8 +17,6 @@ import { lazy } from "@/util/lazy" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false -initProjectors() - const log = Log.create({ service: "server" }) export type Listener = { diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 82ad7aaa81..379e41f0bf 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -16,7 +16,6 @@ import { Effect, Layer, Context, Schema } from "effect" import * as DateTime from "effect/DateTime" import { InstanceState } from "@/effect/instance-state" import { isOverflow as overflow, usable } from "./overflow" -import { makeRuntime } from "@/effect/run-service" import { serviceUse } from "@/effect/service-use" // kilocode_change start import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue" @@ -26,8 +25,8 @@ import { SessionExport } from "@/kilocode/session-export" import { KiloSession } from "@/kilocode/session" // kilocode_change end import { RuntimeFlags } from "@/effect/runtime-flags" -import { SyncEvent } from "@/sync" -import { SessionEvent } from "@/v2/session-event" +import { EventV2Bridge } from "@/event-v2-bridge" +import { SessionEvent } from "@opencode-ai/core/session-event" const log = Log.create({ service: "session.compaction" }) @@ -145,7 +144,7 @@ function buildPrompt(input: { previousSummary?: string; context: string[] }) { return [anchor, SUMMARY_TEMPLATE, ...input.context].join("\n\n") } -function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model }) { +function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model; outputTokenMax?: number }) { // kilocode_change return ( input.cfg.compaction?.preserve_recent_tokens ?? Math.min(MAX_PRESERVE_RECENT_TOKENS, Math.max(MIN_PRESERVE_RECENT_TOKENS, Math.floor(usable(input) * 0.25))) @@ -221,19 +220,7 @@ export class Service extends Context.Service()("@opencode/Se export const use = serviceUse(Service) -export const layer: Layer.Layer< - Service, - never, - | Bus.Service - | Config.Service - | Session.Service - | Agent.Service - | Plugin.Service - | SessionProcessor.Service - | Provider.Service - | SyncEvent.Service - | RuntimeFlags.Service -> = Layer.effect( +export const layer = Layer.effect( Service, Effect.gen(function* () { const bus = yield* Bus.Service @@ -243,14 +230,19 @@ export const layer: Layer.Layer< const plugin = yield* Plugin.Service const processors = yield* SessionProcessor.Service const provider = yield* Provider.Service - const sync = yield* SyncEvent.Service + const events = yield* EventV2Bridge.Service const flags = yield* RuntimeFlags.Service const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: { tokens: MessageV2.Assistant["tokens"] model: Provider.Model }) { - return overflow({ cfg: yield* config.get(), tokens: input.tokens, model: input.model }) + return overflow({ + cfg: yield* config.get(), + tokens: input.tokens, + model: input.model, + outputTokenMax: flags.outputTokenMax, + }) }) const estimate = Effect.fn("SessionCompaction.estimate")(function* (input: { @@ -268,7 +260,13 @@ export const layer: Layer.Layer< }) { const limit = input.cfg.compaction?.tail_turns ?? DEFAULT_TAIL_TURNS if (limit <= 0) return { head: input.messages, tail_start_id: undefined } - const budget = preserveRecentBudget({ cfg: input.cfg, model: input.model }) + // kilocode_change start + const budget = preserveRecentBudget({ + cfg: input.cfg, + model: input.model, + outputTokenMax: flags.outputTokenMax, + }) + // kilocode_change end const all = turns(input.messages) if (!all.length) return { head: input.messages, tail_start_id: undefined } const recent = all.slice(-limit) @@ -470,7 +468,7 @@ export const layer: Layer.Layer< model, }) // kilocode_change start - const result = KiloCompactionChunks.needed({ cfg, model, tokens }) + const result = KiloCompactionChunks.needed({ cfg, model, tokens, outputTokenMax: flags.outputTokenMax }) ? "compact" : yield* KiloCompactionPayloadRecovery.process({ processor, @@ -497,6 +495,7 @@ export const layer: Layer.Layer< sessionID: input.sessionID, model, cfg, + outputTokenMax: flags.outputTokenMax, messages: selected.head, prompt: nextPrompt, target: processor.message, @@ -523,8 +522,7 @@ export const layer: Layer.Layer< }) } - if (fallback === "continue" && input.auto) { - // kilocode_change + if (fallback === "continue" && input.auto) { // kilocode_change if (replay) { // kilocode_change start - compact oversized replay turns instead of looping into replay overflow replay = yield* KiloCompactionChunks.replay({ @@ -535,6 +533,7 @@ export const layer: Layer.Layer< sessionID: input.sessionID, model, cfg, + outputTokenMax: flags.outputTokenMax, messages: selected.head, prompt: nextPrompt, target: processor.message, @@ -641,7 +640,7 @@ export const layer: Layer.Layer< }, ) if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Compaction.Ended.Sync, { + yield* events.publish(SessionEvent.Compaction.Ended, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), text: summary ?? "", @@ -681,8 +680,8 @@ export const layer: Layer.Layer< yield* prune({ sessionID: input.sessionID, reason: "post-compaction" }) yield* bus.publish(Event.Compacted, { sessionID: input.sessionID }) } + return fallback // kilocode_change end - return fallback // kilocode_change }) const create = Effect.fn("SessionCompaction.create")(function* (input: { @@ -712,7 +711,7 @@ export const layer: Layer.Layer< KiloSessionPromptQueue.retarget(input.sessionID, msg.id) // kilocode_change end if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Compaction.Started.Sync, { + yield* events.publish(SessionEvent.Compaction.Started, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), reason: input.auto ? "auto" : "manual", @@ -723,7 +722,7 @@ export const layer: Layer.Layer< return Service.of({ isOverflow, prune, - process: (input) => processCompaction(input).pipe(Effect.orDie), + process: (input) => processCompaction(input).pipe(Effect.orDie), // kilocode_change create, }) }), @@ -738,20 +737,9 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(Plugin.defaultLayer), Layer.provide(Bus.layer), Layer.provide(Config.defaultLayer), - Layer.provide(SyncEvent.defaultLayer), Layer.provide(RuntimeFlags.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), ), ) -const { runPromise } = makeRuntime(Service, defaultLayer) - -export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) { - return runPromise((svc) => svc.isOverflow(input)) -} - -export async function prune(input: { sessionID: SessionID; reason?: PruneReason }) { - // kilocode_change - return runPromise((svc) => svc.prune(input)) -} - export * as SessionCompaction from "./compaction" diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index de3e0e0875..bc67f7dfbd 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -3,6 +3,7 @@ import { Effect, Layer, Context } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http" import { Config } from "@/config/config" import { InstanceState } from "@/effect/instance-state" +import { RuntimeFlags } from "@/effect/runtime-flags" import { Flag } from "@opencode-ai/core/flag/flag" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { withTransientReadRetry } from "@/util/effect-http-client" @@ -11,9 +12,9 @@ import { KilocodeInstruction } from "@/kilocode/session/instruction" // kilocode import type { MessageV2 } from "./message-v2" import type { MessageID } from "./schema" -const FILES = [ +const files = (disableClaudeCodePrompt: boolean) => [ "AGENTS.md", - ...(Flag.KILO_DISABLE_CLAUDE_CODE_PROMPT ? [] : ["CLAUDE.md"]), + ...(disableClaudeCodePrompt ? [] : ["CLAUDE.md"]), "CONTEXT.md", // deprecated ] @@ -51,21 +52,23 @@ export class Service extends Context.Service()("@opencode/In export const layer: Layer.Layer< Service, never, - AppFileSystem.Service | Config.Service | Global.Service | HttpClient.HttpClient + AppFileSystem.Service | Config.Service | Global.Service | HttpClient.HttpClient | RuntimeFlags.Service > = Layer.effect( Service, Effect.gen(function* () { const cfg = yield* Config.Service const fs = yield* AppFileSystem.Service const global = yield* Global.Service + const flags = yield* RuntimeFlags.Service const http = HttpClient.filterStatusOk(withTransientReadRetry(yield* HttpClient.HttpClient)) const globalFiles = [ // kilocode_change start - prefer KILO_CONFIG_DIR profile when set ...(Flag.KILO_CONFIG_DIR ? [path.join(Flag.KILO_CONFIG_DIR, "AGENTS.md")] : []), // kilocode_change end path.join(global.config, "AGENTS.md"), - ...(!Flag.KILO_DISABLE_CLAUDE_CODE_PROMPT ? [path.join(global.home, ".claude", "CLAUDE.md")] : []), + ...(!flags.disableClaudeCodePrompt ? [path.join(global.home, ".claude", "CLAUDE.md")] : []), ] + const instructionFiles = files(flags.disableClaudeCodePrompt) const state = yield* InstanceState.make( Effect.fn("Instruction.state")(() => @@ -122,8 +125,10 @@ export const layer: Layer.Layer< // The first project-level match wins so we don't stack AGENTS.md/CLAUDE.md from every ancestor. if (!Flag.KILO_DISABLE_PROJECT_CONFIG) { - for (const file of FILES) { - const matches = yield* fs.findUp(file, ctx.directory, ctx.worktree) + for (const file of instructionFiles) { + const matches = yield* fs + .findUp(file, ctx.directory, ctx.worktree) + .pipe(Effect.catch(() => Effect.succeed([]))) if (matches.length > 0) { matches.forEach((item) => paths.add(path.resolve(item))) break @@ -168,7 +173,7 @@ export const layer: Layer.Layer< }) const find = Effect.fn("Instruction.find")(function* (dir: string) { - for (const file of FILES) { + for (const file of instructionFiles) { const filepath = path.resolve(path.join(dir, file)) if (yield* fs.existsSafe(filepath)) return filepath } @@ -228,6 +233,7 @@ export const defaultLayer = layer.pipe( Layer.provide(Global.layer), Layer.provide(AppFileSystem.defaultLayer), Layer.provide(FetchHttpClient.layer), + Layer.provide(RuntimeFlags.defaultLayer), ) export function loaded(messages: MessageV2.WithParts[]) { diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index a473552a61..d3baeb8c4b 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -134,7 +134,9 @@ const live: Layer.Layer< const system: string[] = [] system.push( [ + // kilocode_change start - soul defines core identity and personality ...(isOpenaiOauth ? [] : [SystemPrompt.soul()]), + // kilocode_change end // use agent prompt otherwise provider prompt ...(input.agent.prompt ? [input.agent.prompt] : SystemPrompt.provider(input.model)), // any custom prompt passed into this call @@ -172,7 +174,9 @@ const live: Layer.Layer< }) const options = mergeOptions(mergeOptions(mergeOptions(base, input.model.options), input.agent.options), variant) if (isOpenaiOauth) { + // kilocode_change start - prepend soul to instructions options.instructions = SystemPrompt.soul() + "\n" + system.join("\n") + // kilocode_change end } const isWorkflow = language instanceof GitLabWorkflowLanguageModel @@ -211,7 +215,7 @@ const live: Layer.Layer< maxOutputTokens: input.model.api.npm === "@ai-sdk/openai-compatible" && input.model.api.id.toLowerCase().includes("gpt-5") ? undefined - : ProviderTransform.maxOutputTokens(input.model), + : ProviderTransform.maxOutputTokens(input.model, flags.outputTokenMax), // kilocode_change end options, }, @@ -231,6 +235,7 @@ const live: Layer.Layer< }, ) + // kilocode_change start - resolve project ID and machine ID for kilo provider const isKilo = input.model.api.npm === "@kilocode/kilo-gateway" const kiloProjectId = yield* isKilo ? Effect.promise(() => getKiloProjectId().catch(() => undefined)) @@ -238,6 +243,7 @@ const live: Layer.Layer< const machineId = yield* isKilo ? Effect.promise(() => Identity.getMachineId().catch(() => undefined)) : Effect.succeed(undefined) + // kilocode_change end const tools = resolveTools(input) @@ -290,7 +296,7 @@ const live: Layer.Layer< KiloSessionOverflow.shouldCompact({ cfg, model: input.model, - usable: usable({ cfg, model: input.model }), + usable: usable({ cfg, model: input.model, outputTokenMax: flags.outputTokenMax }), // kilocode_change tokens: usage.normalized, continuation: usage.continuation, }) @@ -340,7 +346,7 @@ const live: Layer.Layer< const bridge = yield* EffectBridge.make() const approvedToolsForSession = new Set() - workflowModel.approvalHandler = InstanceState.bind(async (approvalTools) => { + workflowModel.approvalHandler = bridge.bind(async (approvalTools) => { const uniqueNames = [...new Set(approvalTools.map((t: { name: string }) => t.name))] as string[] // Auto-approve tools that were already approved in this session // (prevents infinite approval loops for server-side MCP tools) @@ -479,7 +485,7 @@ const live: Layer.Layer< maxOutputTokens: params.maxOutputTokens, abortSignal: input.abort, headers: { - ...(input.model.providerID.startsWith("kilo") + ...(input.model.providerID.startsWith("kilo") // kilocode_change ? { "x-kilo-project": opencodeProjectID, "x-kilo-session": input.sessionID, @@ -491,8 +497,9 @@ const live: Layer.Layer< "x-session-affinity": input.sessionID, ...(input.parentSessionID ? { "x-parent-session-id": input.parentSessionID } : {}), "User-Agent": `opencode/${InstallationVersion}`, - ...(input.model.providerID !== "anthropic" ? DEFAULT_HEADERS : undefined), + ...(input.model.providerID !== "anthropic" ? DEFAULT_HEADERS : undefined), // kilocode_change }), + // kilocode_change start - headers for kilo provider ...(isKilo && input.agent.name ? { "x-kilocode-mode": input.agent.name.toLowerCase() } : {}), ...(isKilo && kiloProjectId ? { [HEADER_PROJECTID]: kiloProjectId } : {}), ...(isKilo && machineId ? { [HEADER_MACHINEID]: machineId } : {}), diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 89c7118692..01c04085cb 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -327,6 +327,7 @@ const messageBase = { sessionID: SessionID, } +// kilocode_change start - shared editor context schema (used by MessageV2.User and SessionPrompt.PromptInput) export const EditorContext = Schema.Struct({ visibleFiles: Schema.optional(Schema.Array(Schema.String)), openTabs: Schema.optional(Schema.Array(Schema.String)), @@ -334,6 +335,7 @@ export const EditorContext = Schema.Struct({ shell: Schema.optional(Schema.String), }) export type EditorContext = Types.DeepMutable> +// kilocode_change end export const User = Schema.Struct({ ...messageBase, @@ -357,7 +359,9 @@ export const User = Schema.Struct({ }), system: Schema.optional(Schema.String), tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), + // kilocode_change start editorContext: Schema.optional(EditorContext), + // kilocode_change end }).annotate({ identifier: "UserMessage" }) export type User = Types.DeepMutable> @@ -589,6 +593,7 @@ export const cursor = { }, } +// kilocode_change start - strip bloated metadata fields from stored parts to prevent multi-MB payloads // This handles both legacy data that was stored with full file contents and keeps the API response lean. function stripPatch(value: unknown) { if (typeof value !== "string") return undefined @@ -602,6 +607,7 @@ function withPatch(value: unknown) { } export function stripPartMetadata(part: Part): Part { + // kilocode_change - exported for testing if (part.type !== "tool") return part const { state } = part if (state.status !== "completed" && state.status !== "running") return part @@ -656,6 +662,7 @@ export function stripPartMetadata(part: Part): Part { } export function stripMessageMetadata(info: Info): Info { + // kilocode_change - exported for testing // Strip oversized summary.diffs patches from user messages to limit SSE payload. // Small patches are preserved so the UI can render inline diffs. if (info.role !== "user") return info @@ -671,7 +678,9 @@ export function stripMessageMetadata(info: Info): Info { }, } as Info } +// kilocode_change end +// kilocode_change - apply stripping inside helpers so all read paths are covered const info = (row: typeof MessageTable.$inferSelect) => stripMessageMetadata({ ...row.data, @@ -686,6 +695,7 @@ const part = (row: typeof PartTable.$inferSelect) => sessionID: row.session_id, messageID: row.message_id, } as Part) +// kilocode_change end const older = (row: Cursor) => or(lt(MessageTable.time_created, row.time), and(eq(MessageTable.time_created, row.time), lt(MessageTable.id, row.id))) @@ -1082,12 +1092,14 @@ export function parts(message_id: MessageID) { ) return rows.map( (row) => + // kilocode_change - apply stripping to parts fetched individually as well to cover all read paths stripPartMetadata({ ...row.data, id: row.id, sessionID: row.session_id, messageID: row.message_id, } as Part), + // kilocode_change end ) } @@ -1222,7 +1234,7 @@ export function fromError( case SessionNetwork.disconnected(e): // kilocode_change start return new APIError( { - message: SessionNetwork.message(e), + message: SessionNetwork.message(e), // kilocode_change end isRetryable: true, metadata: { code: (e as SystemError).code ?? "", diff --git a/packages/opencode/src/session/network.ts b/packages/opencode/src/session/network.ts index cae83ecd3b..ea37342a55 100644 --- a/packages/opencode/src/session/network.ts +++ b/packages/opencode/src/session/network.ts @@ -5,6 +5,9 @@ import { BusEvent } from "../bus/bus-event" import { QuestionID } from "../question/schema" import { SessionID } from "../session/schema" import { InstanceState } from "@/effect/instance-state" +import { InstanceRef } from "@/effect/instance-ref" +import { capture } from "@/kilocode/instance" +import type { InstanceContext } from "@/project/instance-context" import { makeRuntime } from "@/effect/run-service" import * as Log from "@opencode-ai/core/util/log" import { fn } from "@/kilocode/fn" @@ -103,6 +106,7 @@ export namespace SessionNetwork { } interface StateShape { + context: InstanceContext pending: Map< QuestionID, { @@ -122,8 +126,8 @@ export namespace SessionNetwork { StateService, Effect.gen(function* () { const is = yield* InstanceState.make( - Effect.fn("SessionNetwork.state")(function* () { - return { pending: new Map() } as StateShape + Effect.fn("SessionNetwork.state")(function* (ctx) { + return { context: ctx, pending: new Map() } as StateShape }), ) return StateService.of({ @@ -133,7 +137,11 @@ export namespace SessionNetwork { ) const stateRuntime = makeRuntime(StateService, stateLayer) - const state = (): Promise => stateRuntime.runPromise((svc) => svc.get()) + const state = (): Promise => { + const ctx = capture() + if (!ctx) return Promise.reject(new Error("Instance context not available")) + return stateRuntime.runPromise((svc) => svc.get().pipe(Effect.provideService(InstanceRef, ctx))) + } export function code(err: unknown) { for (const item of chain(err)) { @@ -272,7 +280,7 @@ export namespace SessionNetwork { if (!s.pending.has(id)) return input.abort.removeEventListener("abort", onAbort) s.pending.delete(id) - Bus.publish(Event.Rejected, { + void Bus.publish(s.context, Event.Rejected, { sessionID: input.sessionID, requestID: id, }) @@ -296,7 +304,7 @@ export namespace SessionNetwork { return } log.warn("waiting for network", { sessionID: input.sessionID, requestID: id, message: input.message }) - Bus.publish(Event.Asked, info) + void Bus.publish(s.context, Event.Asked, info) void watch({ requestID: id, abort: input.abort }).catch((err) => { log.error("restore watch failed", { err, requestID: id }) }) @@ -317,7 +325,7 @@ export namespace SessionNetwork { req.info.restored = true req.info.time = { ...req.info.time, restored: time } log.info("network restored", { sessionID: req.info.sessionID, requestID }) - Bus.publish(Event.Restored, { + await Bus.publish(s.context, Event.Restored, { sessionID: req.info.sessionID, requestID: req.info.id, time, @@ -369,7 +377,7 @@ export namespace SessionNetwork { log.error("failed to get MCP status for reconnect", { err }) }) // kilocode_change end - Bus.publish(Event.Replied, { + await Bus.publish(s.context, Event.Replied, { sessionID: req.info.sessionID, requestID: req.info.id, }) @@ -390,7 +398,7 @@ export namespace SessionNetwork { return } s.pending.delete(requestID) - Bus.publish(Event.Rejected, { + await Bus.publish(s.context, Event.Rejected, { sessionID: req.info.sessionID, requestID: req.info.id, }) diff --git a/packages/opencode/src/session/overflow.ts b/packages/opencode/src/session/overflow.ts index 353daa1589..6b3204a9af 100644 --- a/packages/opencode/src/session/overflow.ts +++ b/packages/opencode/src/session/overflow.ts @@ -6,18 +6,24 @@ import { KiloSessionOverflow } from "@/kilocode/session/overflow" // kilocode_ch const COMPACTION_BUFFER = 20_000 -export function usable(input: { cfg: Config.Info; model: Provider.Model }) { +export function usable(input: { cfg: Config.Info; model: Provider.Model; outputTokenMax?: number }) { const context = input.model.limit.context if (context === 0) return 0 const reserved = - input.cfg.compaction?.reserved ?? Math.min(COMPACTION_BUFFER, ProviderTransform.maxOutputTokens(input.model)) + input.cfg.compaction?.reserved ?? + Math.min(COMPACTION_BUFFER, ProviderTransform.maxOutputTokens(input.model, input.outputTokenMax)) return input.model.limit.input ? Math.max(0, input.model.limit.input - reserved) - : Math.max(0, context - ProviderTransform.maxOutputTokens(input.model)) + : Math.max(0, context - ProviderTransform.maxOutputTokens(input.model, input.outputTokenMax)) } -export function isOverflow(input: { cfg: Config.Info; tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) { +export function isOverflow(input: { + cfg: Config.Info + tokens: MessageV2.Assistant["tokens"] + model: Provider.Model + outputTokenMax?: number +}) { if (input.cfg.compaction?.auto === false) return false if (input.model.limit.context === 0) return false diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index f0de313e32..39a9020d21 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -18,16 +18,15 @@ import { SessionStatus } from "./status" import { SessionSummary } from "./summary" import type { Provider } from "@/provider/provider" import { Question } from "@/question" -// kilocode_change start import { KiloSessionProcessor, type ReviewTelemetry } from "@/kilocode/session/processor" import { KiloSessionOverflow } from "@/kilocode/session/overflow" import { Suggestion } from "@/kilocode/suggestion" -// kilocode_change end import { errorMessage } from "@/util/error" import * as Log from "@opencode-ai/core/util/log" import { isRecord } from "@/util/record" -import { SyncEvent } from "@/sync" -import { SessionEvent } from "@/v2/session-event" +import { EventV2 } from "@opencode-ai/core/event" +import { EventV2Bridge } from "@/event-v2-bridge" +import { SessionEvent } from "@opencode-ai/core/session-event" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" import * as DateTime from "effect/DateTime" @@ -56,17 +55,15 @@ export interface Handle { }, ) => Effect.Effect readonly process: (streamInput: LLM.StreamInput) => Effect.Effect - readonly compactError?: () => ReturnType | undefined // kilocode_change + readonly compactError?: () => ReturnType | undefined } type Input = { assistantMessage: MessageV2.Assistant sessionID: SessionID model: Provider.Model - // kilocode_change start telemetry?: ReviewTelemetry snapshotInitialization?: "wait" - // kilocode_change end } export interface Interface { @@ -86,36 +83,18 @@ interface ProcessorContext extends Input { snapshot: string | undefined blocked: boolean needsCompaction: boolean - compactionError: ReturnType | undefined // kilocode_change + compactionError: ReturnType | undefined currentText: MessageV2.TextPart | undefined reasoningMap: Record - // kilocode_change start stepStart: number step: { reasoning: boolean; text: boolean; tool: boolean } - // kilocode_change end } type StreamEvent = Event export class Service extends Context.Service()("@opencode/SessionProcessor") {} -export const layer: Layer.Layer< - Service, - never, - | Session.Service - | Config.Service - | Bus.Service - | Snapshot.Service - | Agent.Service - | LLM.Service - | Permission.Service - | Plugin.Service - | Image.Service - | SessionSummary.Service - | SessionStatus.Service - | SyncEvent.Service - | RuntimeFlags.Service -> = Layer.effect( +export const layer = Layer.effect( Service, Effect.gen(function* () { const session = yield* Session.Service @@ -130,20 +109,18 @@ export const layer: Layer.Layer< const scope = yield* Scope.Scope const status = yield* SessionStatus.Service const image = yield* Image.Service - const sync = yield* SyncEvent.Service + const events = yield* EventV2Bridge.Service const flags = yield* RuntimeFlags.Service const create = Effect.fn("SessionProcessor.create")(function* (input: Input) { // Pre-capture snapshot before the LLM stream starts. The AI SDK // may execute tools internally before emitting start-step events, // so capturing inside the event handler can be too late. - // kilocode_change start - pass turn context for slow-snapshot UI/policy handling const initialSnapshot = yield* snapshot.track({ sessionID: input.sessionID, messageID: input.assistantMessage.id, snapshotInitialization: input.snapshotInitialization, }) - // kilocode_change end const ctx: ProcessorContext = { assistantMessage: input.assistantMessage, sessionID: input.sessionID, @@ -153,17 +130,15 @@ export const layer: Layer.Layer< snapshot: initialSnapshot, blocked: false, needsCompaction: false, - compactionError: undefined, // kilocode_change + compactionError: undefined, currentText: undefined, reasoningMap: {}, - // kilocode_change start telemetry: input.telemetry, stepStart: 0, step: { reasoning: false, text: false, tool: false }, - // kilocode_change end } let aborted = false - const ac = new AbortController() // kilocode_change — abort controller for offline handler + const ac = new AbortController() const slog = log.clone().tag("session.id", input.sessionID).tag("messageID", input.assistantMessage.id) const parse = (e: unknown) => @@ -193,7 +168,6 @@ export const layer: Layer.Layer< return { call, part } }) - // kilocode_change start - tolerate deleted sessions during subagent cost reconciliation (#6321) const reconcile = Effect.fn("SessionProcessor.reconcileCost")(function* () { const fresh = yield* MessageV2.get({ sessionID: ctx.assistantMessage.sessionID, @@ -203,7 +177,6 @@ export const layer: Layer.Layer< if (fresh.info.cost <= ctx.assistantMessage.cost) return ctx.assistantMessage.cost = fresh.info.cost }) - // kilocode_change end const updateToolCall = Effect.fn("SessionProcessor.updateToolCall")(function* ( toolCallID: string, @@ -244,11 +217,9 @@ export const layer: Layer.Layer< attachments: output.attachments, }, }) - // kilocode_change start - accepted suggest review actions tag following LLM completion telemetry if (match.part.tool === "suggest") { ctx.telemetry = KiloSessionProcessor.suggestionReviewTelemetry(output.metadata) ?? ctx.telemetry } - // kilocode_change end yield* settleToolCall(toolCallID) }) @@ -261,17 +232,15 @@ export const layer: Layer.Layer< status: "error", input: match.part.state.input, error: errorMessage(error), - metadata: match.part.state.metadata, // kilocode_change - preserve running tool metadata on failure + metadata: match.part.state.metadata, time: { start: match.part.state.time.start, end: Date.now() }, }, }) - // kilocode_change start if ( error instanceof Permission.RejectedError || error instanceof Question.RejectedError || error instanceof Suggestion.DismissedError ) { - // kilocode_change end ctx.blocked = ctx.shouldBreak } yield* settleToolCall(toolCallID) @@ -286,10 +255,10 @@ export const layer: Layer.Layer< case "reasoning-start": if (value.id in ctx.reasoningMap) return - ctx.step.reasoning = true // kilocode_change + ctx.step.reasoning = true // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Reasoning.Started.Sync, { + yield* events.publish(SessionEvent.Reasoning.Started, { sessionID: ctx.sessionID, reasoningID: value.id, timestamp: DateTime.makeUnsafe(Date.now()), @@ -324,7 +293,7 @@ export const layer: Layer.Layer< if (!(value.id in ctx.reasoningMap)) return // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Reasoning.Ended.Sync, { + yield* events.publish(SessionEvent.Reasoning.Ended, { sessionID: ctx.sessionID, reasoningID: value.id, text: ctx.reasoningMap[value.id].text, @@ -343,10 +312,10 @@ export const layer: Layer.Layer< if (ctx.assistantMessage.summary) { throw new Error(`Tool call not allowed while generating summary: ${value.toolName}`) } - ctx.step.tool = true // kilocode_change + ctx.step.tool = true // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Tool.Input.Started.Sync, { + yield* events.publish(SessionEvent.Tool.Input.Started, { sessionID: ctx.sessionID, callID: value.id, name: value.toolName, @@ -377,7 +346,7 @@ export const layer: Layer.Layer< case "tool-input-end": { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Tool.Input.Ended.Sync, { + yield* events.publish(SessionEvent.Tool.Input.Ended, { sessionID: ctx.sessionID, callID: value.id, text: "", @@ -391,7 +360,6 @@ export const layer: Layer.Layer< if (ctx.assistantMessage.summary) { throw new Error(`Tool call not allowed while generating summary: ${value.toolName}`) } - // kilocode_change start ctx.step.tool = true if (!ctx.toolcalls[value.toolCallId]) { log.warn("tool-call without prior tool-input-start", { @@ -414,11 +382,10 @@ export const layer: Layer.Layer< sessionID: part.sessionID, } } - // kilocode_change end const toolCall = yield* readToolCall(value.toolCallId) // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Tool.Called.Sync, { + yield* events.publish(SessionEvent.Tool.Called, { sessionID: ctx.sessionID, callID: value.toolCallId, tool: value.toolName, @@ -506,7 +473,7 @@ export const layer: Layer.Layer< } // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Tool.Success.Sync, { + yield* events.publish(SessionEvent.Tool.Success, { sessionID: ctx.sessionID, callID: value.toolCallId, structured: output.metadata, @@ -529,11 +496,9 @@ export const layer: Layer.Layer< }) } yield* completeToolCall(value.toolCallId, output) - // kilocode_change start - dismissed suggestions stop the turn after persisting normalized output if (value.output.metadata?.dismissed === true) { ctx.blocked = ctx.shouldBreak } - // kilocode_change end return } @@ -541,7 +506,7 @@ export const layer: Layer.Layer< const toolCall = yield* readToolCall(value.toolCallId) // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Tool.Failed.Sync, { + yield* events.publish(SessionEvent.Tool.Failed, { sessionID: ctx.sessionID, callID: value.toolCallId, error: { @@ -562,7 +527,6 @@ export const layer: Layer.Layer< throw value.error case "start-step": - // kilocode_change start ctx.stepStart = performance.now() ctx.step = { reasoning: false, text: false, tool: false } if (!ctx.snapshot) @@ -571,11 +535,10 @@ export const layer: Layer.Layer< messageID: ctx.assistantMessage.id, snapshotInitialization: input.snapshotInitialization, }) - // kilocode_change end if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Step.Started.Sync, { + yield* events.publish(SessionEvent.Step.Started, { sessionID: ctx.sessionID, agent: input.assistantMessage.agent, model: { @@ -598,19 +561,16 @@ export const layer: Layer.Layer< return case "finish-step": { - // kilocode_change start - pass turn context for slow-snapshot UI/policy handling const completedSnapshot = yield* snapshot.track({ sessionID: ctx.sessionID, messageID: ctx.assistantMessage.id, snapshotInitialization: input.snapshotInitialization, }) - // kilocode_change end const usage = Session.getUsage({ model: ctx.model, usage: value.usage, metadata: value.providerMetadata, }) - // kilocode_change start - guard against finish-step without start-step: // ctx.stepStart is 0 until `start-step` fires, which would feed a // huge bogus `elapsed` into telemetry. Fall back to now(). KiloSessionProcessor.trackStep({ @@ -621,11 +581,10 @@ export const layer: Layer.Layer< elapsed: Math.round(performance.now() - (ctx.stepStart || performance.now())), telemetry: ctx.telemetry, }) - // kilocode_change end if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Step.Ended.Sync, { + yield* events.publish(SessionEvent.Step.Ended, { sessionID: ctx.sessionID, finish: value.finishReason, cost: usage.cost, @@ -636,9 +595,7 @@ export const layer: Layer.Layer< } } ctx.assistantMessage.finish = value.finishReason - // kilocode_change start - capture any subagent cost propagated by tool calls during this step (#6321) yield* reconcile() - // kilocode_change end ctx.assistantMessage.cost += usage.cost ctx.assistantMessage.tokens = usage.tokens yield* session.updatePart({ @@ -651,7 +608,6 @@ export const layer: Layer.Layer< tokens: usage.tokens, cost: usage.cost, }) - // kilocode_change start - surface output limit stops, with a stronger message for reasoning-only stops const warn = KiloSessionProcessor.lengthWarning({ msg: ctx.assistantMessage, step: ctx.step }) if (warn) { yield* session.updatePart({ @@ -671,7 +627,6 @@ export const layer: Layer.Layer< }) yield* status.set(ctx.sessionID, { type: "idle" }) } - // kilocode_change end yield* session.updateMessage(ctx.assistantMessage) if (ctx.snapshot) { const patch = yield* snapshot.patch(ctx.snapshot) @@ -695,14 +650,17 @@ export const layer: Layer.Layer< .pipe(Effect.ignore, Effect.forkIn(scope)) if ( !ctx.assistantMessage.summary && - isOverflow({ cfg: yield* config.get(), tokens: usage.tokens, model: ctx.model }) + isOverflow({ + cfg: yield* config.get(), + tokens: usage.tokens, + model: ctx.model, + outputTokenMax: flags.outputTokenMax, + }) ) { ctx.needsCompaction = true - // kilocode_change start ctx.compactionError = new MessageV2.ContextOverflowError({ message: "Input exceeds context window of this model", }).toObject() - // kilocode_change end } return } @@ -711,7 +669,7 @@ export const layer: Layer.Layer< if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Text.Started.Sync, { + yield* events.publish(SessionEvent.Text.Started, { sessionID: ctx.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), }) @@ -732,7 +690,7 @@ export const layer: Layer.Layer< case "text-delta": if (!ctx.currentText) return ctx.currentText.text += value.text - if (value.text.trim()) ctx.step.text = true // kilocode_change + if (value.text.trim()) ctx.step.text = true if (value.providerMetadata) ctx.currentText.metadata = value.providerMetadata yield* session.updatePartDelta({ sessionID: ctx.currentText.sessionID, @@ -756,11 +714,11 @@ export const layer: Layer.Layer< }, { text: ctx.currentText.text }, )).text - if (ctx.currentText.text.trim()) ctx.step.text = true // kilocode_change + if (ctx.currentText.text.trim()) ctx.step.text = true if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Text.Ended.Sync, { + yield* events.publish(SessionEvent.Text.Ended, { sessionID: ctx.sessionID, text: ctx.currentText.text, timestamp: DateTime.makeUnsafe(Date.now()), @@ -841,26 +799,20 @@ export const layer: Layer.Layer< }) } ctx.toolcalls = {} - KiloSessionProcessor.guardEmptyToolCalls(ctx.assistantMessage, MessageV2.parts(ctx.assistantMessage.id)) // kilocode_change + KiloSessionProcessor.guardEmptyToolCalls(ctx.assistantMessage, MessageV2.parts(ctx.assistantMessage.id)) ctx.assistantMessage.time.completed = Date.now() - // kilocode_change start - reconcile cost with any subagent propagation written during tool calls (#6321) yield* reconcile() - // kilocode_change end yield* session.updateMessage(ctx.assistantMessage) }) const halt = Effect.fn("SessionProcessor.halt")(function* (e: unknown) { - // kilocode_change start - internal preflight signal, not a provider error if (e instanceof KiloSessionOverflow.PreflightError) { ctx.needsCompaction = true return } - // kilocode_change end slog.error("process", { error: errorMessage(e), stack: e instanceof Error ? e.stack : undefined }) const error = parse(e) - // kilocode_change start ctx.compactionError = MessageV2.ContextOverflowError.isInstance(error) ? error : ctx.compactionError - // kilocode_change end if (MessageV2.ContextOverflowError.isInstance(error)) { ctx.needsCompaction = true yield* bus.publish(Session.Event.Error, { sessionID: ctx.sessionID, error }) @@ -869,7 +821,7 @@ export const layer: Layer.Layer< if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Step.Failed.Sync, { + yield* events.publish(SessionEvent.Step.Failed, { sessionID: ctx.sessionID, error: { type: "unknown", @@ -887,16 +839,14 @@ export const layer: Layer.Layer< yield* status.set(ctx.sessionID, { type: "idle" }) }) - // kilocode_change start const output = { compactError: () => ctx.compactionError, } - // kilocode_change end const process = Effect.fn("SessionProcessor.process")(function* (streamInput: LLM.StreamInput) { slog.info("process") ctx.needsCompaction = false - ctx.compactionError = undefined // kilocode_change + ctx.compactionError = undefined // czcode_change start - lh-* agents always continue on deny; dismissing a dialog should not halt the session const isLhAgent = ctx.assistantMessage.agent?.startsWith("lh-") ?? false ctx.shouldBreak = isLhAgent ? false : (yield* config.get()).experimental?.continue_loop_on_deny !== true @@ -906,13 +856,11 @@ export const layer: Layer.Layer< yield* Effect.gen(function* () { ctx.currentText = undefined ctx.reasoningMap = {} - ctx.step = { reasoning: false, text: false, tool: false } // kilocode_change - // kilocode_change start + ctx.step = { reasoning: false, text: false, tool: false } const stream = llm.stream({ ...streamInput, preflight: !ctx.assistantMessage.summary, }) - // kilocode_change end yield* stream.pipe( Stream.tap((event) => handleEvent(event)), @@ -923,7 +871,7 @@ export const layer: Layer.Layer< Effect.onInterrupt(() => Effect.gen(function* () { aborted = true - ac.abort() // kilocode_change — also abort offline handler + ac.abort() if (!ctx.assistantMessage.error) { yield* halt(new DOMException("Aborted", "AbortError")) } @@ -937,13 +885,11 @@ export const layer: Layer.Layer< SessionRetry.policy({ provider: input.model.providerID, parse, - // kilocode_change start ...KiloSessionProcessor.retryOpts({ sessionID: ctx.sessionID, abort: ac.signal, set: status.set }), - // kilocode_change end set: (info) => { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. const event = flags.experimentalEventSystem - ? sync.run(SessionEvent.Retried.Sync, { + ? events.publish(SessionEvent.Retried, { sessionID: ctx.sessionID, attempt: info.attempt, error: { @@ -983,7 +929,7 @@ export const layer: Layer.Layer< }, updateToolCall, completeToolCall, - ...output, // kilocode_change + ...output, process, } satisfies Handle }) @@ -1005,8 +951,8 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(Image.defaultLayer), Layer.provide(Bus.layer), Layer.provide(Config.defaultLayer), - Layer.provide(SyncEvent.defaultLayer), Layer.provide(RuntimeFlags.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), ), ) diff --git a/packages/opencode/src/session/projectors-next.ts b/packages/opencode/src/session/projectors-next.ts index 8938a9b91d..c1ab13859e 100644 --- a/packages/opencode/src/session/projectors-next.ts +++ b/packages/opencode/src/session/projectors-next.ts @@ -1,10 +1,11 @@ import { and, desc, eq } from "@/storage/db" import type { Database } from "@/storage/db" -import { SessionMessage } from "@/v2/session-message" -import { SessionMessageUpdater } from "@/v2/session-message-updater" -import { SessionEvent } from "@/v2/session-event" +import { SessionMessage } from "@opencode-ai/core/session-message" +import { SessionMessageUpdater } from "@opencode-ai/core/session-message-updater" +import { SessionEvent } from "@opencode-ai/core/session-event" import * as DateTime from "effect/DateTime" import { SyncEvent } from "@/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import { SessionMessageTable, SessionTable } from "./session.sql" import type { SessionID } from "./schema" import { Schema } from "effect" @@ -138,7 +139,7 @@ function update(db: Database.TxOrDb, event: SessionEvent.Event) { } export default [ - SyncEvent.project(SessionEvent.AgentSwitched.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.AgentSwitched), (db, data, event) => { db.update(SessionTable) .set({ agent: data.agent, @@ -148,7 +149,7 @@ export default [ .run() update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.agent.switched", data }) }), - SyncEvent.project(SessionEvent.ModelSwitched.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.ModelSwitched), (db, data, event) => { db.update(SessionTable) .set({ model: data.model, @@ -158,65 +159,65 @@ export default [ .run() update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.model.switched", data }) }), - SyncEvent.project(SessionEvent.Prompted.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Prompted), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.prompted", data }) }), - SyncEvent.project(SessionEvent.Synthetic.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Synthetic), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.synthetic", data }) }), - SyncEvent.project(SessionEvent.Shell.Started.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Shell.Started), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.shell.started", data }) }), - SyncEvent.project(SessionEvent.Shell.Ended.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Shell.Ended), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.shell.ended", data }) }), - SyncEvent.project(SessionEvent.Step.Started.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Started), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.started", data }) }), - SyncEvent.project(SessionEvent.Step.Ended.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Ended), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.ended", data }) }), - SyncEvent.project(SessionEvent.Step.Failed.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Failed), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.failed", data }) }), - SyncEvent.project(SessionEvent.Text.Started.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Started), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.text.started", data }) }), - SyncEvent.project(SessionEvent.Text.Delta.Sync, () => {}), - SyncEvent.project(SessionEvent.Text.Ended.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Delta), () => {}), + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Ended), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.text.ended", data }) }), - SyncEvent.project(SessionEvent.Tool.Input.Started.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Started), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.input.started", data }) }), - SyncEvent.project(SessionEvent.Tool.Input.Delta.Sync, () => {}), - SyncEvent.project(SessionEvent.Tool.Input.Ended.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Delta), () => {}), + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Ended), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.input.ended", data }) }), - SyncEvent.project(SessionEvent.Tool.Called.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Called), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.called", data }) }), - SyncEvent.project(SessionEvent.Tool.Success.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Success), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.success", data }) }), - SyncEvent.project(SessionEvent.Tool.Failed.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Failed), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.failed", data }) }), - SyncEvent.project(SessionEvent.Reasoning.Started.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Started), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.reasoning.started", data }) }), - SyncEvent.project(SessionEvent.Reasoning.Delta.Sync, () => {}), - SyncEvent.project(SessionEvent.Reasoning.Ended.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Delta), () => {}), + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Ended), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.reasoning.ended", data }) }), - SyncEvent.project(SessionEvent.Retried.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Retried), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.retried", data }) }), - SyncEvent.project(SessionEvent.Compaction.Started.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Started), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.compaction.started", data }) }), - SyncEvent.project(SessionEvent.Compaction.Delta.Sync, () => {}), - SyncEvent.project(SessionEvent.Compaction.Ended.Sync, (db, data, event) => { + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Delta), () => {}), + SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Ended), (db, data, event) => { update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.compaction.ended", data }) }), ] diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 68bc5cfefe..841e6835e7 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1,17 +1,17 @@ import path from "path" import os from "os" import fs from "fs/promises" -import { KiloSessionPrompt } from "@/kilocode/session/prompt" // kilocode_change -import { KiloSessionMessageOrder } from "@/kilocode/session/message-order" // kilocode_change -import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue" // kilocode_change -import { KiloSession } from "@/kilocode/session" // kilocode_change -import { KiloCostPropagation } from "@/kilocode/session/cost-propagation" // kilocode_change -import { KiloSessionProcessor } from "@/kilocode/session/processor" // kilocode_change -import { CommandTimeout } from "@/kilocode/command-timeout" // kilocode_change -import { Suggestion } from "@/kilocode/suggestion" // kilocode_change -import { Question } from "@/question" // kilocode_change -import { zod } from "@opencode-ai/core/effect-zod" // kilocode_change -import { withStatics } from "@opencode-ai/core/schema" // kilocode_change +import { KiloSessionPrompt } from "@/kilocode/session/prompt" +import { KiloSessionMessageOrder } from "@/kilocode/session/message-order" +import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue" +import { KiloSession } from "@/kilocode/session" +import { KiloCostPropagation } from "@/kilocode/session/cost-propagation" +import { KiloSessionProcessor } from "@/kilocode/session/processor" +import { CommandTimeout } from "@/kilocode/command-timeout" +import { Suggestion } from "@/kilocode/suggestion" +import { Question } from "@/question" +import { zod } from "@opencode-ai/core/effect-zod" +import { withStatics } from "@opencode-ai/core/schema" import { SessionID, MessageID, PartID } from "./schema" import type { NotFoundError } from "@/storage/storage" import { MessageV2 } from "./message-v2" @@ -56,15 +56,16 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Truncate } from "@/tool/truncate" import { Image } from "@/image/image" import { decodeDataUrl } from "@/util/data-url" -import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect" // kilocode_change - Process moved to the timeout helper +import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect" import * as EffectLogger from "@opencode-ai/core/effect/logger" import { InstanceState } from "@/effect/instance-state" import { TaskTool, type TaskPromptOps } from "@/tool/task" import { SessionRunState } from "./run-state" import { EffectBridge } from "@/effect/bridge" -import { SyncEvent } from "@/sync" // kilocode_change - preserve Kilo v2 event dual-write wiring import { RuntimeFlags } from "@/effect/runtime-flags" -import { SessionEvent } from "@/v2/session-event" +import { EventV2 } from "@opencode-ai/core/event" +import { EventV2Bridge } from "@/event-v2-bridge" +import { SessionEvent } from "@opencode-ai/core/session-event" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" import { AgentAttachment, FileAttachment, ReferenceAttachment, Source } from "@opencode-ai/core/session-prompt" @@ -91,12 +92,9 @@ IMPORTANT: const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.` -// kilocode_change export const shouldAskPlanFollowup = KiloSessionPrompt.shouldAskPlanFollowup -// kilocode_change start - persistent tool-output pruning when payload is already large const REQUEST_PRUNE_BYTES = 1_250_000 -// kilocode_change end const log = Log.create({ service: "session.prompt" }) const elog = EffectLogger.create({ service: "session.prompt" }) @@ -208,7 +206,7 @@ export const layer = Layer.effect( const commands = yield* Command.Service const config = yield* Config.Service const permission = yield* Permission.Service - const question = yield* Question.Service // kilocode_change - dismiss superseded pending questions through the shared service + const question = yield* Question.Service const fsys = yield* AppFileSystem.Service const mcp = yield* MCP.Service const lsp = yield* LSP.Service @@ -224,7 +222,7 @@ export const layer = Layer.effect( const sys = yield* SystemPrompt.Service const llm = yield* LLM.Service const references = yield* Reference.Service - const sync = yield* SyncEvent.Service // kilocode_change - preserve Kilo v2 event dual-write wiring + const events = yield* EventV2Bridge.Service const flags = yield* RuntimeFlags.Service const runner = Effect.fn("SessionPrompt.runner")(function* () { return yield* EffectBridge.make() @@ -240,10 +238,10 @@ export const layer = Layer.effect( const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) { yield* elog.info("cancel", { sessionID }) - yield* KiloSessionPromptQueue.cancel(sessionID) // kilocode_change - drop queued follow-up loops on abort - KiloSessionPrompt.abortPlanFollowup(sessionID) // kilocode_change - abort pending plan-followup handover work - yield* state.cancel(sessionID) + yield* KiloSessionPromptQueue.cancel(sessionID) + KiloSessionPrompt.abortPlanFollowup(sessionID) Telemetry.track(TelemetryEvent.MESSAGE_ABORTED, { sessionID }) // czcode_change — passive ALHF signal + yield* state.cancel(sessionID) }) const resolvePromptParts = Effect.fn("SessionPrompt.resolvePromptParts")(function* (template: string) { @@ -382,7 +380,7 @@ export const layer = Layer.effect( small: true, tools: {}, model: mdl, - sessionID: KiloSessionPrompt.titleID(input.session.id), // kilocode_change - isolate title requests from the agent task + sessionID: KiloSessionPrompt.titleID(input.session.id), retries: 2, messages: [{ role: "user", content: "Generate a title for this conversation:\n" }, ...msgs], }) @@ -413,7 +411,6 @@ export const layer = Layer.effect( if (!userMessage) return input.messages if (!flags.experimentalPlanMode) { - // kilocode_change start - inject plan file path so agent writes to .kilo/plans/ yield* Effect.promise(() => KiloSessionPrompt.insertPlanReminders({ agent: input.agent, @@ -422,16 +419,14 @@ export const layer = Layer.effect( messages: input.messages, }), ) - // kilocode_change end const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan") if (wasPlan && input.agent.name === "code") { - // kilocode_change - renamed from "build" to "code" userMessage.parts.push({ id: PartID.ascending(), messageID: userMessage.info.id, sessionID: userMessage.info.sessionID, type: "text", - text: CODE_SWITCH, // kilocode_change - renamed from BUILD_SWITCH to CODE_SWITCH + text: CODE_SWITCH, synthetic: true, }) } @@ -448,7 +443,7 @@ export const layer = Layer.effect( messageID: userMessage.info.id, sessionID: userMessage.info.sessionID, type: "text", - text: `${CODE_SWITCH}\n\nA plan file exists at ${plan}. You should execute on the plan defined within it`, // kilocode_change - renamed from BUILD_SWITCH to CODE_SWITCH + text: `${CODE_SWITCH}\n\nA plan file exists at ${plan}. You should execute on the plan defined within it`, synthetic: true, }) userMessage.parts.push(part) @@ -578,7 +573,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the }, } }), - // kilocode_change start - resolve permissions at ask time so active tools see config edits ask: (req) => KiloSessionPrompt.askPermission({ permission, @@ -592,7 +586,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the tool: { messageID: input.processor.message.id, callID: options.toolCallId }, }, }).pipe(Effect.orDie), - // kilocode_change end }) for (const item of yield* registry.tools({ @@ -797,12 +790,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the let error: Error | undefined const taskAbort = new AbortController() - // kilocode_change start - shared reader for the child session id written by task.ts ctx.metadata (#6321) const childID = () => { const meta = part.state.status !== "pending" ? part.state.metadata : undefined return (meta as { sessionId?: string } | undefined)?.sessionId } - // kilocode_change end const result = yield* taskTool .execute(taskArgs, { agent: task.agent, @@ -820,7 +811,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the state: { ...part.state, ...val }, } satisfies MessageV2.ToolPart) }), - // kilocode_change start - resolve permissions at ask time so active tools see config edits ask: (req: any) => KiloSessionPrompt.askPermission({ permission, @@ -833,7 +823,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the sessionID, }, }).pipe(Effect.orDie), - // kilocode_change end }) .pipe( Effect.catchCause((cause) => { @@ -847,12 +836,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the taskAbort.abort() assistantMessage.finish = "tool-calls" assistantMessage.time.completed = Date.now() - // kilocode_change start - propagate partial subagent cost on cancel (#6321) const cid = childID() if (cid) { assistantMessage.cost = yield* KiloCostPropagation.childCost(sessions, SessionID.make(cid)) } - // kilocode_change end yield* sessions.updateMessage(assistantMessage) if (part.state.status === "running") { yield* sessions.updatePart({ @@ -885,12 +872,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the assistantMessage.finish = "tool-calls" assistantMessage.time.completed = Date.now() - // kilocode_change start - include subagent total cost on the wrapper message (#6321) const cid = result?.metadata?.sessionId ?? childID() if (cid) { assistantMessage.cost = yield* KiloCostPropagation.childCost(sessions, SessionID.make(cid)) } - // kilocode_change end yield* sessions.updateMessage(assistantMessage) if (result && part.state.status === "running") { @@ -933,7 +918,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the time: { created: Date.now() }, agent: lastUser.agent, model: lastUser.model, - editorContext: lastUser.editorContext, // kilocode_change — preserve editor context + editorContext: lastUser.editorContext, } yield* sessions.updateMessage(summaryUserMsg) yield* sessions.updatePart({ @@ -964,7 +949,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() }) throw error } - const model = input.model ?? agent.model ?? (yield* currentModel(input.sessionID)) // kilocode_change + const model = input.model ?? agent.model ?? (yield* currentModel(input.sessionID)) const userMsg: MessageV2.User = { id: input.messageID ?? MessageID.ascending(), sessionID: input.sessionID, @@ -999,7 +984,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the providerID: model.providerID, } yield* sessions.updateMessage(msg) - const callID = ulid() // kilocode_change - correlate v2 shell events with the persisted tool part + const callID = ulid() const started = Date.now() const part: MessageV2.ToolPart = { type: "tool", @@ -1007,7 +992,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the messageID: msg.id, sessionID: input.sessionID, tool: ShellID.ToolID, - callID, // kilocode_change + callID, state: { status: "running", time: { start: started }, @@ -1015,16 +1000,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the }, } yield* sessions.updatePart(part) - // kilocode_change start - preserve Kilo v2 shell event dual-write if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Shell.Started.Sync, { + yield* events.publish(SessionEvent.Shell.Started, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(started), - callID, + callID: part.callID, command: input.command, }) } - // kilocode_change end return { msg, part, cwd: ctx.directory } }).pipe(Effect.ensuring(markReady)) @@ -1033,25 +1016,23 @@ NOTE: At any point in time through this workflow you should feel free to ask the const args = Shell.args(sh, input.command, cwd) let output = "" let aborted = false - let timeout: string | undefined // kilocode_change + let timeout: string | undefined const finish = Effect.uninterruptible( Effect.gen(function* () { if (aborted) { output += "\n\n" + ["", "User aborted the command", ""].join("\n") } - if (timeout) output += "\n\n" + ["", timeout, ""].join("\n") // kilocode_change + if (timeout) output += "\n\n" + ["", timeout, ""].join("\n") const completed = Date.now() - // kilocode_change start - preserve Kilo v2 shell event dual-write if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Shell.Ended.Sync, { + yield* events.publish(SessionEvent.Shell.Ended, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(completed), callID: part.callID, output, }) } - // kilocode_change end if (!msg.time.completed) { msg.time.completed = completed yield* sessions.updateMessage(msg) @@ -1085,7 +1066,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the forceKillAfter: "3 seconds", }) const handle = yield* spawner.spawn(cmd) - // kilocode_change start timeout = yield* CommandTimeout.drain( handle, Stream.runForEach(Stream.decodeText(handle.all), (chunk) => @@ -1099,7 +1079,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the ), "shell command terminated", ) - // kilocode_change end }).pipe(Effect.scoped, Effect.orDie), ).pipe(Effect.exit) @@ -1127,18 +1106,17 @@ NOTE: At any point in time through this workflow you should feel free to ask the const err = Cause.squash(exit.cause) if (Provider.ModelNotFoundError.isInstance(err)) { const hint = err.suggestions?.length ? ` Did you mean: ${err.suggestions.join(", ")}?` : "" - const empty = err.modelsEmpty ? " No models are currently available." : "" // kilocode_change + const empty = err.modelsEmpty ? " No models are currently available." : "" yield* bus.publish(Session.Event.Error, { sessionID, error: new NamedError.Unknown({ - message: `Model not found: ${err.providerID}/${err.modelID}.${hint}${empty}`, // kilocode_change + message: `Model not found: ${err.providerID}/${err.modelID}.${hint}${empty}`, }).toObject(), }) } return yield* Effect.die(err) }) - // kilocode_change start - preserve persisted per-session model selection const currentModel = Effect.fnUntraced(function* (sessionID: SessionID) { const current = Database.use((db) => db.select({ model: SessionTable.model }).from(SessionTable).where(eq(SessionTable.id, sessionID)).get(), @@ -1156,7 +1134,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model return yield* provider.defaultModel() }) - // kilocode_change end const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) { const agentName = input.agent @@ -1176,7 +1153,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the .where(eq(SessionTable.id, input.sessionID)) .get(), ) - const model = input.model ?? ag.model ?? (yield* currentModel(input.sessionID)) // kilocode_change + const model = input.model ?? ag.model ?? (yield* currentModel(input.sessionID)) const same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID const full = !input.variant && ag.variant && same @@ -1200,11 +1177,11 @@ NOTE: At any point in time through this workflow you should feel free to ask the }, system: input.system, format: input.format, - editorContext: input.editorContext, // kilocode_change + editorContext: input.editorContext, } if (current?.agent !== info.agent) { - yield* sync.run(SessionEvent.AgentSwitched.Sync, { + yield* events.publish(SessionEvent.AgentSwitched, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(info.time.created), agent: info.agent, @@ -1215,7 +1192,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the current.model.id !== info.model.modelID || (current.model.variant === "default" ? undefined : current.model.variant) !== info.model.variant ) { - yield* sync.run(SessionEvent.ModelSwitched.Sync, { + yield* events.publish(SessionEvent.ModelSwitched, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(info.time.created), model: { @@ -1336,7 +1313,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the { ...part, messageID: info.id, sessionID: input.sessionID }, ] } - // kilocode_change start - normalize user image data before persistence if (part.mime.startsWith("image/")) { const file: MessageV2.FilePart = { ...part, @@ -1346,7 +1322,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the } return [yield* image.normalize(file).pipe(Effect.orDie)] } - // kilocode_change end break case "file:": { log.info("file", { mime: part.mime }) @@ -1455,7 +1430,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (mime === "application/x-directory") { const args = { filePath: filepath } - const exit = yield* execRead(args, { includeDirectoryFiles: true }).pipe(Effect.exit) // kilocode_change inline folder files + const exit = yield* execRead(args, { includeDirectoryFiles: true }).pipe(Effect.exit) if (Exit.isFailure(exit)) { const error = Cause.squash(exit.cause) log.error("failed to read directory", { error }) @@ -1499,7 +1474,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the ] } - // kilocode_change start - reject oversized user image files before reading and base64 allocation if (mime.startsWith("image/")) { const limit = (yield* config.get()).attachment?.image?.max_base64_bytes ?? Image.MAX_BASE64_BYTES const stat = yield* fsys.stat(filepath).pipe(Effect.catch(Effect.die)) @@ -1516,7 +1490,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the }), ) } - // kilocode_change end const file: MessageV2.FilePart = { id: part.id ? PartID.make(part.id) : PartID.ascending(), messageID: info.id, @@ -1529,9 +1502,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the filename: part.filename!, source: part.source, } - // kilocode_change start - apply image limits after resolving user file URLs const attachment = mime.startsWith("image/") ? yield* image.normalize(file).pipe(Effect.orDie) : file - // kilocode_change end return [ ...(referenceContext ? [{ ...referenceContext, messageID: info.id, sessionID: input.sessionID }] : []), { @@ -1568,7 +1539,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the return [{ ...part, messageID: info.id, sessionID: input.sessionID }] }) - // kilocode_change start - resolve and persist the exact transformed Kilo prompt parts const resolvedParts = yield* Effect.forEach(input.parts, resolvePart, { concurrency: "unbounded" }).pipe( Effect.map((x) => x.flat().map(assign)), ) @@ -1586,7 +1556,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the ) const parts = resolvedParts - // kilocode_change end const parsed = decodeMessageInfo(info, { errors: "all", propertyOrder: "original" }) if (Exit.isFailure(parsed)) { @@ -1680,10 +1649,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the synthetic: [] as string[], }, ) - // kilocode_change start - preserve Kilo v2 prompt event dual-write // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Prompted.Sync, { + yield* events.publish(SessionEvent.Prompted, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(info.time.created), prompt: { @@ -1697,14 +1665,13 @@ NOTE: At any point in time through this workflow you should feel free to ask the for (const text of nextPrompt.synthetic) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { - yield* sync.run(SessionEvent.Synthetic.Sync, { + yield* events.publish(SessionEvent.Synthetic, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(info.time.created), text, }) } } - // kilocode_change end return { info, parts } }, Effect.scoped) @@ -1713,10 +1680,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the function* (input: PromptInput) { const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie) yield* revert.cleanup(session) - // kilocode_change start - recover interrupted Kilo turns before accepting a follow-up yield* KiloSessionPrompt.recoverDanglingAssistant({ sessionID: input.sessionID, status, sessions }) yield* KiloSessionPrompt.recoverProviderFinishError({ sessionID: input.sessionID, status, sessions }) - // kilocode_change end const message = yield* createUserMessage(input) yield* sessions.touch(input.sessionID) @@ -1725,17 +1690,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" }) } if (permissions.length > 0) { - // kilocode_change start - preserve inherited task restrictions while refreshing prompt tool toggles const merged = KiloSessionPrompt.mergeToolPermissions({ existing: session.permission ?? [], toggles: permissions, }) session.permission = merged yield* sessions.setPermission({ sessionID: session.id, permission: merged }) - // kilocode_change end } - // kilocode_change start — unblock tools waiting on user input so any in-flight // handle.process can return. Adding a new user message is the signal that any // pending tool prompt is superseded, so we dismiss even on the noReply path. // Critically we never cancel the in-flight fiber here — that would abort the @@ -1753,17 +1715,17 @@ NOTE: At any point in time through this workflow you should feel free to ask the input.sessionID, message.info.id, bridge.run( - loop({ sessionID: input.sessionID, snapshotInitialization: input.snapshotInitialization }).pipe(Effect.orDie), - ), // kilocode_change + loop({ sessionID: input.sessionID, snapshotInitialization: input.snapshotInitialization }).pipe( + Effect.orDie, + ), + ), bridge.run(lastAssistant(input.sessionID)), ) - // kilocode_change end }, Effect.catchTag("NotFoundError", Effect.die), ) const lastAssistant = Effect.fnUntraced(function* (sessionID: SessionID) { - // kilocode_change start - retry when cancel races before shellImpl writes messages for (let attempt = 0; attempt < 10; attempt++) { const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user") if (Option.isSome(match)) return match.value @@ -1771,25 +1733,18 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (msgs.length > 0) return msgs[0] yield* Effect.sleep("50 millis") } - // kilocode_change end throw new Error("Impossible") }) - // kilocode_change — mutable close-reason per session, set by runLoop and read by loop const closeReasons = new Map() - // kilocode_change start - retain request-scoped snapshot initialization policy const runLoop: (input: LoopInput) => Effect.Effect = Effect.fn( "SessionPrompt.run", - )(function* ( - input: LoopInput, - ) { + )(function* (input: LoopInput) { const sessionID = input.sessionID - // kilocode_change end - // kilocode_change — cache environment details per turn (prompt caching) const envCache: KiloSessionPrompt.EnvCache = {} - closeReasons.delete(sessionID) // kilocode_change - let compactionAttempts = 0 // kilocode_change - cap compaction attempts per turn to avoid infinite loops + closeReasons.delete(sessionID) + let compactionAttempts = 0 // czcode_change start - track consecutive all-failure steps to break lh-* agent dead loops let consecutiveFailureSteps = 0 const LH_FAILURE_STEP_LIMIT = 5 @@ -1805,42 +1760,33 @@ NOTE: At any point in time through this workflow you should feel free to ask the yield* slog.info("loop", { step }) let msgs = yield* MessageV2.filterCompactedEffect(sessionID) - msgs = KiloSessionPromptQueue.scope(sessionID, msgs) // kilocode_change - hide later queued prompts - msgs = KiloSessionPrompt.trimBeforeLastSummary(msgs) // kilocode_change - trim on any completed summary (e.g. manual /compact against a text user) + msgs = KiloSessionPromptQueue.scope(sessionID, msgs) + msgs = KiloSessionPrompt.trimBeforeLastSummary(msgs) - // kilocode_change start - select loop state by chronology after retained-tail projection const latest = KiloSessionMessageOrder.latest(msgs) const { user: lastUser, assistant: lastAssistant, finished: lastFinished, tasks } = latest - // kilocode_change end if (!lastUser) throw new Error("No user message found in stream. This should never happen.") const lastAssistantMsg = msgs.findLast( (msg) => msg.info.role === "assistant" && msg.info.id === lastAssistant?.id, ) - // kilocode_change start - compare chronology, not generated IDs const userBeforeAssistant = latest.userMessage && latest.assistantMessage && KiloSessionMessageOrder.compare(latest.userMessage, latest.assistantMessage) < 0 - // kilocode_change end - // kilocode_change start - carry local review command marker into LLM telemetry const telemetry = KiloSessionProcessor.extractReviewTelemetry( msgs.findLast((m) => m.info.role === "user" && m.info.id === lastUser.id)?.parts ?? [], ) ?? KiloSessionProcessor.extractSuggestionReviewTelemetry(lastAssistantMsg?.parts ?? []) - // kilocode_change end - // kilocode_change start - keep provider-executed tools from forcing a re-loop // Some providers return "stop" even when the assistant message contains tool calls. // Keep the loop running so tool results can be sent back to the model. // Skip provider-executed tool parts — those were fully handled within the // provider's stream (e.g. DWS Agent Platform) and don't need a re-loop. const hasToolCalls = lastAssistantMsg?.parts.some((part) => part.type === "tool" && !part.metadata?.providerExecuted) ?? false - // kilocode_change end - // kilocode_change start - plan_exit is a hard stop before another model call if ( lastAssistant?.finish && hasToolCalls && @@ -1855,21 +1801,18 @@ NOTE: At any point in time through this workflow you should feel free to ask the yield* slog.info("exiting loop") break } - // kilocode_change end if ( lastAssistant?.finish && !["tool-calls"].includes(lastAssistant.finish) && !hasToolCalls && - lastAssistant.parentID === lastUser.id && // kilocode_change - unrelated later assistants do not answer this turn - userBeforeAssistant // kilocode_change - compare chronology, not generated IDs + lastAssistant.parentID === lastUser.id && + userBeforeAssistant ) { - // kilocode_change start - ask follow-up when plan_exit tool was called const action = yield* Effect.promise((signal) => KiloSessionPrompt.askPlanFollowup({ sessionID, messages: msgs, abort: signal, question }), ) if (action === "continue") continue - // kilocode_change end yield* slog.info("exiting loop") break } @@ -1899,13 +1842,11 @@ NOTE: At any point in time through this workflow you should feel free to ask the auto: task.auto, overflow: task.overflow, }) - // kilocode_change start - compaction.process only returns "stop" after // setting ContextOverflowError on the summary message; surface as turn error if (result === "stop") { closeReasons.set(sessionID, "error") break } - // kilocode_change end continue } @@ -1914,7 +1855,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the lastFinished.summary !== true && (yield* compaction.isOverflow({ tokens: lastFinished.tokens, model })) ) { - // kilocode_change start const guard = KiloSessionPrompt.guardCompactionAttempt({ sessionID, attempts: compactionAttempts, @@ -1929,7 +1869,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the break } compactionAttempts++ - // kilocode_change end yield* compaction.create({ sessionID, agent: lastUser.agent, model: lastUser.model, auto: true }) continue } @@ -1976,8 +1915,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the assistantMessage: msg, sessionID, model, - telemetry, // kilocode_change - snapshotInitialization: input.snapshotInitialization, // kilocode_change + telemetry, + snapshotInitialization: input.snapshotInitialization, }) .pipe(Effect.onInterrupt(() => finalize)) @@ -2009,11 +1948,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (step > 1 && lastFinished) { for (const m of msgs) { - // kilocode_change start - compare chronology, not generated IDs const finishedBeforeMessage = latest.finishedMessage && KiloSessionMessageOrder.compare(latest.finishedMessage, m) < 0 if (m.info.role !== "user" || !finishedBeforeMessage) continue - // kilocode_change end for (const p of m.parts) { if (p.type !== "text" || p.ignored || p.synthetic) continue if (!p.text.trim()) continue @@ -2031,17 +1968,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) - // kilocode_change start — ephemeral context injection + post-summary // media strip (keeps outgoing body under the gateway body-size limit // even when filterCompacted couldn't trim the pre-summary history). KiloSessionPrompt.injectEditorContext({ msgs, lastUser, sessionID, cache: envCache }) msgs = KiloSessionPrompt.maybeStripHistoricalMedia(msgs) - // kilocode_change end - // kilocode_change start - persistently prune stale tool outputs when payload is already large const [skills, env, instructions] = yield* Effect.all([ sys.skills(agent), - sys.environment(model, lastUser.editorContext), // kilocode_change + sys.environment(model, lastUser.editorContext), instruction.system().pipe(Effect.orDie), ]) let modelMsgs = yield* MessageV2.toModelMessagesEffect(msgs, model) @@ -2058,17 +1992,13 @@ NOTE: At any point in time through this workflow you should feel free to ask the const nextSize = Buffer.byteLength(JSON.stringify(modelMsgs)) if (nextSize > REQUEST_PRUNE_BYTES) log.warn("payload still large after pruning", { size: nextSize }) } - // kilocode_change end const system = [...env, ...instructions, ...(skills ? [skills] : [])] const format = lastUser.format ?? { type: "text" as const } - if (format.type === "json_schema") system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT) // kilocode_change + if (format.type === "json_schema") system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT) const result = yield* handle.process({ - // kilocode_change - // kilocode_change start - keep Ask/Plan tool filtering hardened against session allows user: lastUser, agent, permission: KiloSessionPrompt.guardPermissions({ agent, session }), - // kilocode_change end sessionID, parentSessionID: session.parentID, system, @@ -2095,44 +2025,19 @@ NOTE: At any point in time through this workflow you should feel free to ask the yield* sessions.updateMessage(handle.message) return "break" as const } - // kilocode_change start if (handle.message.finish === "error") { KiloSessionProcessor.providerFinishError(handle.message) yield* sessions.updateMessage(handle.message) closeReasons.set(sessionID, "error") return "break" as const } - // kilocode_change end - // czcode_change start - break dead loop: if lh-* agent produces only failed tool calls - // for LH_FAILURE_STEP_LIMIT consecutive steps, stop the loop so the user sees the error - if (agent.name.startsWith("lh-")) { - const toolParts = MessageV2.parts(handle.message.id).filter( - (p): p is MessageV2.ToolPart => p.type === "tool", - ) - const allFailed = - toolParts.length > 0 && toolParts.every((p) => p.state.status === "error") - if (allFailed) { - consecutiveFailureSteps++ - if (consecutiveFailureSteps >= LH_FAILURE_STEP_LIMIT) { - consecutiveFailureSteps = 0 - closeReasons.set(sessionID, "error") - return "break" as const - } - } else { - consecutiveFailureSteps = 0 - } - } - // czcode_change end } - // kilocode_change start if (result === "stop") { if (handle.message.error) closeReasons.set(sessionID, "error") return "break" as const } - // kilocode_change end if (result === "compact") { - // kilocode_change start const guard = KiloSessionPrompt.guardCompactionAttempt({ sessionID, attempts: compactionAttempts, @@ -2145,17 +2050,14 @@ NOTE: At any point in time through this workflow you should feel free to ask the return "break" as const } compactionAttempts++ - // kilocode_change end yield* compaction.create({ sessionID, agent: lastUser.agent, model: lastUser.model, auto: true, - // kilocode_change - preflight compaction replays the pending turn without treating media as provider overflow - overflow: !handle.message.finish && handle.compactError?.() !== undefined, // kilocode_change + overflow: !handle.message.finish && handle.compactError?.() !== undefined, }) } - // kilocode_change start — break out so a newer queued prompt can take over // instead of starting another LLM step for the now-superseded turn. The // current handle.process has fully drained (tokens + inline tool calls) by // the time we get here, so nothing is cut off. @@ -2163,8 +2065,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the closeReasons.set(sessionID, "interrupted") return "break" as const } - // kilocode_change end - // kilocode_change start - guard against providers that end the stream // without a terminal stop_reason (e.g. an Anthropic-style message_delta // with stop_reason: null followed immediately by message_stop). Without // a finishReason, the loop-exit check at the top of the next iteration @@ -2178,7 +2078,25 @@ NOTE: At any point in time through this workflow you should feel free to ask the handle.message.finish = "unknown" yield* sessions.updateMessage(handle.message) } - // kilocode_change end + // czcode_change start - break dead loop: if lh-* agent produces only failed tool calls + // for LH_FAILURE_STEP_LIMIT consecutive steps, stop the loop so the user sees the error + if (agent.name.startsWith("lh-")) { + const toolParts = MessageV2.parts(handle.message.id).filter( + (p): p is MessageV2.ToolPart => p.type === "tool", + ) + const allFailed = toolParts.length > 0 && toolParts.every((p) => p.state.status === "error") + if (allFailed) { + consecutiveFailureSteps++ + if (consecutiveFailureSteps >= LH_FAILURE_STEP_LIMIT) { + consecutiveFailureSteps = 0 + closeReasons.set(sessionID, "error") + return "break" as const + } + } else { + consecutiveFailureSteps = 0 + } + } + // czcode_change end return "continue" as const }).pipe( Effect.ensuring(instruction.clear(handle.message.id)), @@ -2194,10 +2112,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the const loop: (input: LoopInput) => Effect.Effect = Effect.fn( "SessionPrompt.loop", - )(function* ( - input: LoopInput, - ) { - // kilocode_change start + )(function* (input: LoopInput) { yield* KiloSessionPrompt.recoverDanglingAssistant({ sessionID: input.sessionID, status, sessions }) yield* KiloSessionPrompt.recoverProviderFinishError({ sessionID: input.sessionID, status, sessions }) yield* bus.publish(KiloSession.Event.TurnOpen, { sessionID: input.sessionID }) @@ -2206,7 +2121,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the input.sessionID, lastAssistant(input.sessionID).pipe(Effect.orDie), runLoop(input).pipe(Effect.orDie), - ), // kilocode_change + ), Effect.fnUntraced(function* (exit) { yield* bus.publish(KiloSession.Event.TurnClose, { sessionID: input.sessionID, @@ -2218,7 +2133,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the }) }), ) - // kilocode_change end }) const shell: (input: ShellInput) => Effect.Effect = Effect.fn( @@ -2274,12 +2188,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (shellMatches.length > 0) { const cfg = yield* config.get() const sh = Shell.preferred(cfg.shell) - // kilocode_change start const results = yield* CommandTimeout.texts( shellMatches.map(([, cmd]) => cmd), sh, ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner)) - // kilocode_change end let index = 0 template = template.replace(bashRegex, () => results[index++]) } @@ -2292,7 +2204,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (cmdAgent?.model) return cmdAgent.model } if (input.model) return Provider.parseModel(input.model) - return yield* currentModel(input.sessionID) // kilocode_change + return yield* currentModel(input.sessionID) }) yield* getModel(taskModel.providerID, taskModel.modelID, input.sessionID) @@ -2307,7 +2219,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the } const templateParts = yield* resolvePromptParts(template) - KiloSessionProcessor.markReviewTelemetry(templateParts, input.command) // kilocode_change - mark review commands for completion telemetry + KiloSessionProcessor.markReviewTelemetry(templateParts, input.command) const isSubtask = (agent.mode === "subagent" && cmd.subtask !== false) || cmd.subtask === true const parts = isSubtask ? [ @@ -2326,7 +2238,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the const userModel = isSubtask ? input.model ? Provider.parseModel(input.model) - : yield* currentModel(input.sessionID) // kilocode_change + : yield* currentModel(input.sessionID) : taskModel yield* plugin.trigger( @@ -2342,7 +2254,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the agent: userAgent, parts, variant: input.variant, - snapshotInitialization: input.snapshotInitialization, // kilocode_change + snapshotInitialization: input.snapshotInitialization, }) yield* bus.publish(Command.Event.Executed, { name: input.command, @@ -2373,14 +2285,13 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(SessionProcessor.defaultLayer), Layer.provide(Command.defaultLayer), Layer.provide(Permission.defaultLayer), - Layer.provide(Question.defaultLayer), // kilocode_change - provide pending question dismissal dependency + Layer.provide(Question.defaultLayer), Layer.provide(MCP.defaultLayer), Layer.provide(LSP.defaultLayer), Layer.provide(ToolRegistry.defaultLayer), Layer.provide(Truncate.defaultLayer), ) .pipe( - Layer.provide(Image.defaultLayer), // kilocode_change - provide user image normalization service Layer.provide(Provider.defaultLayer), Layer.provide(Config.defaultLayer), Layer.provide(Instruction.defaultLayer), @@ -2389,15 +2300,16 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(Session.defaultLayer), Layer.provide(SessionRevert.defaultLayer), Layer.provide(SessionSummary.defaultLayer), + Layer.provide(Image.defaultLayer), Layer.provide( Layer.mergeAll( + EventV2Bridge.defaultLayer, Agent.defaultLayer, SystemPrompt.defaultLayer, LLM.defaultLayer, Reference.defaultLayer, Bus.layer, CrossSpawnSpawner.defaultLayer, - SyncEvent.defaultLayer, // kilocode_change - provide Kilo v2 event dual-write service RuntimeFlags.defaultLayer, ), ), @@ -2421,14 +2333,10 @@ export const PromptInput = Schema.Struct({ format: Schema.optional(MessageV2.Format), system: Schema.optional(Schema.String), variant: Schema.optional(Schema.String), - // kilocode_change start - managed product slow-snapshot policy snapshotInitialization: Schema.optional(Schema.Literal("wait")).annotate({ description: "Wait silently if snapshot initialization is slow instead of asking the user.", }), - // kilocode_change end - // kilocode_change start - reuse shared editor context schema editorContext: Schema.optional(MessageV2.EditorContext), - // kilocode_change end parts: Schema.Array( Schema.Union([ MessageV2.TextPartInput, @@ -2438,7 +2346,6 @@ export const PromptInput = Schema.Struct({ ]).annotate({ discriminator: "type" }), ), }).pipe(withStatics((s) => ({ zod: zod(s) }))) -// kilocode_change start - retain precise prompt input types for Kilo callers // `z.discriminatedUnion` erases the discriminated members' shapes back to // `{}` when walked from the generic `z.ZodType` input. Restore the precise // `parts` type from the exported Schema input types so callers see a proper @@ -2452,11 +2359,10 @@ export type PromptInput = Omit, "parts" | parts: PartInputUnion[] editorContext?: MessageV2.EditorContext } -// kilocode_change end export class LoopInput extends Schema.Class("SessionPrompt.LoopInput")({ sessionID: SessionID, - snapshotInitialization: Schema.optional(Schema.Literal("wait")), // kilocode_change + snapshotInitialization: Schema.optional(Schema.Literal("wait")), }) { static readonly zod = zod(this) } @@ -2478,11 +2384,9 @@ export const CommandInput = Schema.Struct({ arguments: Schema.String, command: Schema.String, variant: Schema.optional(Schema.String), - // kilocode_change start - managed product slow-snapshot policy snapshotInitialization: Schema.optional(Schema.Literal("wait")).annotate({ description: "Wait silently if snapshot initialization is slow instead of asking the user.", }), - // kilocode_change end // Inlined (no identifier annotation) to keep the original SDK output — the // PromptInput call site below references FilePartInput by ref via the // Schema export in message-v2.ts. diff --git a/packages/opencode/src/session/prompt/default.txt b/packages/opencode/src/session/prompt/default.txt index 08216d4690..09a302b6b2 100644 --- a/packages/opencode/src/session/prompt/default.txt +++ b/packages/opencode/src/session/prompt/default.txt @@ -17,11 +17,6 @@ Only use emojis if the user explicitly requests it. Avoid using emojis in all co IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do. IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to. IMPORTANT: Keep your responses short, since they will be displayed on a command line interface. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is .", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". Here are some examples to demonstrate appropriate verbosity: - -user: 2 + 2 -assistant: 4 - - user: what is 2+2? assistant: 4 @@ -43,11 +38,6 @@ assistant: [use the ls tool to list the files in the current directory, then rea npm run dev - -user: How many golf balls fit inside a jetta? -assistant: 150000 - - user: what files are in the directory src/? assistant: [runs ls and sees foo.c, bar.c, baz.c] diff --git a/packages/opencode/src/session/schema.ts b/packages/opencode/src/session/schema.ts index caf8f9d783..f1622b6958 100644 --- a/packages/opencode/src/session/schema.ts +++ b/packages/opencode/src/session/schema.ts @@ -1,15 +1,10 @@ import { Schema } from "effect" import { Identifier } from "@/id/id" +import { Session as CoreSession } from "@opencode-ai/core/session" import { withStatics } from "@opencode-ai/core/schema" -export const SessionID = Schema.String.check(Schema.isStartsWith("ses")).pipe( - Schema.brand("SessionID"), - withStatics((s) => ({ - descending: (id?: string) => s.make(Identifier.descending("session", id)), - })), -) - +export const SessionID = CoreSession.ID export type SessionID = Schema.Schema.Type export const MessageID = Schema.String.check(Schema.isStartsWith("msg")).pipe( diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 7fb71ff39f..6b040f3d79 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -1,7 +1,7 @@ import { sqliteTable, text, integer, index, primaryKey, real } from "drizzle-orm/sqlite-core" import { ProjectTable } from "../project/project.sql" import type { MessageV2 } from "./message-v2" -import type { SessionMessage } from "../v2/session-message" +import type { SessionMessage } from "@opencode-ai/core/session-message" import type { Snapshot } from "../snapshot" import type { Permission } from "../permission" import type { ProjectID } from "../project/schema" diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index d2df634c71..fbcdc2fc38 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -17,9 +17,9 @@ import { PartTable, SessionTable } from "./session.sql" import { Storage } from "@/storage/storage" import * as Log from "@opencode-ai/core/util/log" import { MessageV2 } from "./message-v2" -import type { InstanceContext } from "../project/instance" +import type { InstanceContext } from "../project/instance-context" import { InstanceState } from "@/effect/instance-state" -import { Instance } from "@/project/instance" // kilocode_change - children() uses Instance.current to scope by project_id +import { capture } from "@/kilocode/instance" // kilocode_change - children() scopes by current project when available import { Snapshot } from "@/snapshot" import { ProjectID } from "../project/schema" import { WorkspaceID } from "../control-plane/schema" @@ -618,9 +618,9 @@ export const layer: Layer.Layer< // kilocode_change start - scope by project_id when instance context is available const children = Effect.fn("Session.children")(function* (parentID: SessionID) { - const ctx = yield* Effect.try({ try: () => Instance.current, catch: () => undefined }).pipe(Effect.option) + const ctx = capture() const conditions = [eq(SessionTable.parent_id, parentID)] - if (Option.isSome(ctx)) conditions.push(eq(SessionTable.project_id, ctx.value.project.id)) + if (ctx) conditions.push(eq(SessionTable.project_id, ctx.project.id)) const rows = yield* db((d) => d .select() @@ -669,12 +669,14 @@ export const layer: Layer.Layer< }) const updateMessage = (msg: T): Effect.Effect => - Effect.gen(function* () { + Effect.gen(function* () { // kilocode_change start - ignore FK errors when session was deleted while processor was still running yield* KiloSession.runSyncSafe( - sync.run(MessageV2.Event.Updated, { sessionID: msg.sessionID, info: msg }), - { type: "message update", id: msg.id, sessionID: msg.sessionID }, - ) + sync.run(MessageV2.Event.Updated, { sessionID: msg.sessionID, info: msg }), { + type: "message update", + id: msg.id, + sessionID: msg.sessionID, + }) // kilocode_change end return msg }).pipe(Effect.withSpan("Session.updateMessage")) @@ -737,7 +739,7 @@ export const layer: Layer.Layer< model: input?.model, permission: input?.permission, platform: input?.platform, // kilocode_change - workspaceID: input?.workspaceID ?? workspace, // kilocode_change - allow explicit override + workspaceID: input?.workspaceID ?? workspace, }) return session }) diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index f0928eccbd..ed8847d9b1 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -5,7 +5,6 @@ import { NamedError } from "@opencode-ai/core/util/error" import type { Agent } from "@/agent/agent" import { Bus } from "@/bus" import { InstanceState } from "@/effect/instance-state" -import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" import { Permission } from "@/permission" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -15,7 +14,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags" import { Glob } from "@opencode-ai/core/util/glob" import * as Log from "@opencode-ai/core/util/log" import { Discovery } from "./discovery" -import { rm } from "fs/promises" import { BUILTIN_SKILLS } from "../kilocode/skills/builtin" import CUSTOMIZE_OPENCODE_SKILL_BODY from "./prompt/customize-opencode.md" with { type: "text" } import { isRecord } from "@/util/record" @@ -23,7 +21,6 @@ import { isRecord } from "@/util/record" const log = Log.create({ service: "skill" }) const CLAUDE_EXTERNAL_DIR = ".claude" const AGENTS_EXTERNAL_DIR = ".agents" -// kilocode_change start export const BUILTIN_LOCATION = "builtin" const EXTERNAL_SKILL_PATTERN = "skills/**/SKILL.md" const KILO_SKILL_PATTERN = "{skill,skills}/**/SKILL.md" @@ -170,26 +167,15 @@ const discoverSkills = Effect.fnUntraced(function* ( discovery: Discovery.Interface, fsys: AppFileSystem.Interface, global: Global.Interface, + disableExternalSkills: boolean, disableClaudeCodeSkills: boolean, directory: string, worktree: string, ) { const state: ScanState = { matches: new Set(), dirs: new Set() } - const cfg = yield* config.get() - - // czcode_change start - load order (low→high priority, later overwrites earlier): - // urls → external(global) → external(project) → configDirs(global ~/.czcode etc) → skills.paths(bundled+custom) - for (const url of cfg.skills?.urls ?? []) { - const pulledDirs = yield* discovery.pull(url) - for (const dir of pulledDirs) { - yield* scan(state, dir, SKILL_PATTERN) - } - } - // czcode_change end - const externalDirs: string[] = [] - if (!Flag.KILO_DISABLE_EXTERNAL_SKILLS) { + if (!disableExternalSkills) { if (!disableClaudeCodeSkills) externalDirs.push(CLAUDE_EXTERNAL_DIR) externalDirs.push(AGENTS_EXTERNAL_DIR) @@ -213,6 +199,17 @@ const discoverSkills = Effect.fnUntraced(function* ( yield* scan(state, dir, KILO_SKILL_PATTERN) } + const cfg = yield* config.get() + // czcode_change start - load order (low→high priority, later overwrites earlier): + // urls → external(global) → external(project) → configDirs(global ~/.czcode etc) → skills.paths(bundled+custom) + for (const url of cfg.skills?.urls ?? []) { + const pulledDirs = yield* discovery.pull(url) + for (const dir of pulledDirs) { + yield* scan(state, dir, SKILL_PATTERN) + } + } + // czcode_change end + for (const item of cfg.skills?.paths ?? []) { const expanded = item.startsWith("~/") ? path.join(global.home, item.slice(2)) : item const dir = path.isAbsolute(expanded) ? expanded : path.join(directory, expanded) @@ -266,10 +263,11 @@ export const layer = Layer.effect( discovery, fsys, global, + flags.disableExternalSkills, flags.disableClaudeCodeSkills, ctx.directory, ctx.project.worktree, - ) // kilocode_change + ) }), ) const state = yield* InstanceState.make( @@ -349,13 +347,4 @@ export function fmt(list: Info[], opts: { verbose: boolean }) { ].join("\n") } -export async function remove(location: string) { - if (location === BUILTIN_LOCATION) { - throw new Error("cannot remove built-in skill") - } - const resolved = path.resolve(location) - const dir = path.dirname(resolved) - await rm(dir, { recursive: true, force: true }) -} - export * as Skill from "." diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 5787f07445..d288165587 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -1,4 +1,5 @@ -import { Cause, Duration, Effect, Layer, Schedule, Schema, Semaphore, Struct, Context } from "effect" +import { Cause, Duration, Effect, Layer, Schedule, Schema, Semaphore, Context } from "effect" +import { Struct } from "effect" // kilocode_change import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" @@ -6,16 +7,20 @@ import { AppProcess } from "@opencode-ai/core/process" import { InstanceState } from "@/effect/instance-state" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Hash } from "@opencode-ai/core/util/hash" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" // kilocode_change import { Config } from "@/config/config" import { Global } from "@opencode-ai/core/global" import * as Log from "@opencode-ai/core/util/log" -import { Flag } from "@opencode-ai/core/flag/flag" // kilocode_change -import { DiffFull } from "../kilocode/snapshot/diff-full" // kilocode_change -import { KiloSnapshotTrack } from "../kilocode/snapshot/track" // kilocode_change -import { KiloSnapshotSeed } from "../kilocode/snapshot/seed" // kilocode_change -import type { MessageID, SessionID } from "../session/schema" // kilocode_change -import { withStatics } from "@opencode-ai/core/schema" // kilocode_change -import { zod } from "@opencode-ai/core/effect-zod" // kilocode_change +// kilocode_change start +import { Flag } from "@opencode-ai/core/flag/flag" +import { DiffFull } from "../kilocode/snapshot/diff-full" +import { KiloSnapshotTrack } from "../kilocode/snapshot/track" +import { KiloSnapshotSeed } from "../kilocode/snapshot/seed" +import { KiloSnapshotMaterialize } from "../kilocode/snapshot/materialize" +import type { MessageID, SessionID } from "../session/schema" +import { withStatics } from "@opencode-ai/core/schema" +import { zod } from "@opencode-ai/core/effect-zod" +// kilocode_change end export const Patch = Schema.Struct({ hash: Schema.String, @@ -32,9 +37,11 @@ export const FileDiff = Schema.Struct({ additions: Schema.Finite, deletions: Schema.Finite, status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])), + // kilocode_change start }) .annotate({ identifier: "SnapshotFileDiff" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) // kilocode_change + .pipe(withStatics((s) => ({ zod: zod(s) }))) +// kilocode_change end export type FileDiff = typeof FileDiff.Type // kilocode_change start - lightweight FileDiff without patch for session summaries @@ -42,9 +49,11 @@ export const SummaryFileDiff = FileDiff.mapFields(Struct.omit(["patch"])) .annotate({ identifier: "SnapshotSummaryFileDiff" }) .pipe(withStatics((s) => ({ zod: zod(s) }))) export type SummaryFileDiff = typeof SummaryFileDiff.Type +// kilocode_change end const log = Log.create({ service: "snapshot" }) const prune = "7.days" +const retention = 7 * 24 * 60 * 60 * 1000 // kilocode_change const limit = 2 * 1024 * 1024 const core = ["-c", "core.longpaths=true", "-c", "core.symlinks=true"] const cfg = ["-c", "core.autocrlf=false", ...core] @@ -78,13 +87,17 @@ export interface Interface { export class Service extends Context.Service()("@opencode/Snapshot") {} -export const layer: Layer.Layer = +// kilocode_change start +type Requirements = AppFileSystem.Service | AppProcess.Service | Config.Service | EffectFlock.Service +export const layer: Layer.Layer = + // kilocode_change end Layer.effect( Service, Effect.gen(function* () { const fs = yield* AppFileSystem.Service const appProcess = yield* AppProcess.Service const config = yield* Config.Service + const flock = yield* EffectFlock.Service // kilocode_change const locks = new Map() const lock = (key: string) => { @@ -167,15 +180,26 @@ export const layer: Layer.Layer; root?: boolean }, + ) { + // kilocode_change end if (!files.length) return - const result = yield* git( - [...cfg, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])], - { - cwd: state.directory, - stdin: feed(files), - }, - ) + // kilocode_change start + // A new root snapshot covers the full worktree, so a single pathspec avoids + // quadratic matching against every tracked path in very large repositories. + const cmd = opts?.root + ? ["add", "--all", "--sparse", "--", "."] + : ["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"] + + const result = yield* git([...cfg, ...args(cmd)], { + cwd: state.directory, + env: opts?.env, + stdin: opts?.root ? undefined : feed(files), + }) + // kilocode_change end if (result.code === 0) return log.warn("failed to add snapshot files", { exitCode: result.code, @@ -186,7 +210,11 @@ export const layer: Layer.Layer fs.exists(file).pipe(Effect.orDie) const read = (file: string) => fs.readFileString(file).pipe(Effect.catch(() => Effect.succeed(""))) const remove = (file: string) => fs.remove(file).pipe(Effect.catch(() => Effect.void)) - const locked = (fx: Effect.Effect) => lock(state.gitdir).withPermits(1)(fx) + // kilocode_change start - serialize snapshot repositories across CLI and extension processes + const locked = (fx: Effect.Effect) => + lock(state.gitdir).withPermits(1)(flock.withLock(fx, `snapshot:${state.gitdir}`).pipe(Effect.orDie)) + + // kilocode_change end const enabled = Effect.fnUntraced(function* () { if (state.vcs !== "git") return false @@ -217,7 +245,7 @@ export const layer: Layer.Layer; root?: boolean }) { // kilocode_change yield* sync() const [diff, other] = yield* Effect.all( [ @@ -279,14 +307,34 @@ export const layer: Layer.Layer large.has(item))) yield* sync(Array.from(block)) // Stage only the allowed candidate paths so snapshot updates stay scoped. - yield* stage(allow.filter((item) => !block.has(item))) + // kilocode_change start - initial seeded writes stay protected by the source pin + yield* stage( + allow.filter((item) => !block.has(item)), + opts, + ) }) + const materialize = Effect.fnUntraced(function* () { + yield* locked(KiloSnapshotMaterialize.run({ gitdir: state.gitdir, git, fs }).pipe(Effect.orDie)).pipe( + Effect.timeout("5 minutes"), + Effect.catchCause((cause) => { + log.error("snapshot materialization failed", { cause: Cause.pretty(cause) }) + return Effect.void + }), + Effect.forkDetach, + Effect.asVoid, + ) + }) + // kilocode_change end + const cleanup = Effect.fnUntraced(function* () { return yield* locked( Effect.gen(function* () { if (!(yield* enabled())) return if (!(yield* exists(state.gitdir))) return + // kilocode_change start - retain snapshots for the same seven-day window as object pruning + yield* KiloSnapshotMaterialize.prune({ gitdir: state.gitdir, git, fs }, Date.now() - retention) + // kilocode_change end const result = yield* git(args(["gc", `--prune=${prune}`]), { cwd: state.directory }) if (result.code !== 0) { log.warn("cleanup failed", { @@ -305,6 +353,7 @@ export const layer: Layer.Layer { log.error("cleanup loop failed", { cause: Cause.pretty(cause) }) @@ -818,6 +900,7 @@ export const layer: Layer.Layer s.diff(hash)) }), diffFull: Effect.fn("Snapshot.diffFull")(function* (from: string, to: string) { + // kilocode_change start - cache full diffs at the service boundary if (from === to) return [] const directory = yield* InstanceState.directory const key = `${directory}\0${from}:${to}` @@ -836,6 +919,7 @@ export const layer: Layer.Layer pending) + // kilocode_change end }), }) }), @@ -845,6 +929,7 @@ export const defaultLayer = layer.pipe( Layer.provide(AppProcess.defaultLayer), Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Config.defaultLayer), + Layer.provide(EffectFlock.defaultLayer), // kilocode_change ) export * as Snapshot from "." diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index e1ccdfffd8..2bd256bc51 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -11,7 +11,7 @@ import path from "path" import { readFileSync, readdirSync, existsSync } from "fs" import { Flag } from "@opencode-ai/core/flag/flag" import { InstallationChannel } from "@opencode-ai/core/installation/version" -import { InstanceState } from "@/effect/instance-state" +import { EffectBridge } from "@/effect/bridge" import { init } from "#db" import { Effect, Schema } from "effect" @@ -23,20 +23,19 @@ export const NotFoundError = NamedError.create("NotFoundError", { const log = Log.create({ service: "db" }) -// kilocode_change start - always use czcode.db regardless of channel // czcode_change -type ChannelDbFlags = Pick +type DatabaseFlags = Pick const readRuntimeFlags = () => Effect.runSync(RuntimeFlags.Service.useSync((flags) => flags).pipe(Effect.provide(RuntimeFlags.defaultLayer))) -export function getChannelPath(flags: ChannelDbFlags = readRuntimeFlags()) { +export function getChannelPath(flags: Pick = readRuntimeFlags()) { if (["latest", "beta", "prod"].includes(InstallationChannel) || flags.disableChannelDb) - return path.join(Global.Path.data, "czcode.db") // czcode_change + return path.join(Global.Path.data, "kilo.db") const safe = InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-") return path.join(Global.Path.data, `opencode-${safe}.db`) } -export const getPath = (flags?: ChannelDbFlags) => { +export const getPath = (flags?: Pick) => { if (Flag.KILO_DB) { if (Flag.KILO_DB === ":memory:" || path.isAbsolute(Flag.KILO_DB)) return Flag.KILO_DB return path.join(Global.Path.data, Flag.KILO_DB) @@ -94,7 +93,7 @@ let client: Client | undefined let loaded = false export const Client = Object.assign( - (flags?: ChannelDbFlags): Client => { + (flags: DatabaseFlags = readRuntimeFlags()): Client => { if (loaded) return client as Client const dbPath = getPath(flags) @@ -119,7 +118,7 @@ export const Client = Object.assign( count: entries.length, mode: typeof KILO_MIGRATIONS !== "undefined" ? "bundled" : "dev", }) - if (Flag.KILO_SKIP_MIGRATIONS) { + if (flags.skipMigrations) { for (const item of entries) { item.sql = "select 1;" } @@ -168,7 +167,7 @@ export function use(callback: (trx: TxOrDb) => T): T { } export function effect(fn: () => any | Promise) { - const bound = InstanceState.bind(fn) + const bound = EffectBridge.bind(fn) try { ctx.use().effects.push(bound) } catch { @@ -189,7 +188,7 @@ export function transaction( } catch (err) { if (err instanceof LocalContext.NotFound) { const effects: (() => void | Promise)[] = [] - const txCallback = InstanceState.bind((tx: TxOrDb) => ctx.provide({ tx, effects }, () => callback(tx))) + const txCallback = EffectBridge.bind((tx: TxOrDb) => ctx.provide({ tx, effects }, () => callback(tx))) const result = Client().transaction(txCallback, { behavior: options?.behavior }) for (const effect of effects) effect() return result as NotPromise diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 7f9b8eeef1..0c456f29de 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -1,17 +1,24 @@ +// Legacy sync event system. It should stay unaware of core EventV2 execution; +// the only temporary V2 coupling here is exposing versioned core event schemas +// in effectPayloads() so existing HTTP/SDK schema generation remains stable. +// Remove that registry read when event schemas are generated from core directly. import { Database } from "@/storage/db" import { eq } from "drizzle-orm" import { GlobalBus } from "@/bus/global" import { Bus as ProjectBus } from "@/bus" import { BusEvent } from "@/bus/bus-event" -import type { InstanceContext } from "@/project/instance" +import type { InstanceContext } from "@/project/instance-context" import { EventSequenceTable, EventTable } from "./event.sql" import type { WorkspaceID } from "@/control-plane/schema" import { EventID } from "./schema" import { Context, Effect, Layer, Schema as EffectSchema } from "effect" import type { DeepMutable } from "@opencode-ai/core/schema" +import { EventV2 } from "@opencode-ai/core/event" import { serviceUse } from "@/effect/service-use" import { InstanceState } from "@/effect/instance-state" import { RuntimeFlags } from "@/effect/runtime-flags" +import { attachWith } from "@/effect/run-service" +import * as EventWire from "@/kilocode/event-wire" // kilocode_change // Keep `Event["data"]` mutable because projectors mutate the persisted shape // when writing to the database. Bus payloads (`Properties`) stay readonly — @@ -30,6 +37,7 @@ export type Definition< // passed at definition time (see `session.updated`, whose projector // expands the persisted data to a `{ sessionID, info }` bus payload). properties: BusSchema + wire?: boolean // kilocode_change - EventV2 rows cross persistence and bus boundaries as encoded data } export type Event = { @@ -41,7 +49,12 @@ export type Event = { export type Properties = EffectSchema.Schema.Type -export type SerializedEvent = Event & { type: string } +// kilocode_change start - serialized rows carry the schema's encoded representation +export type SerializedEvent = Omit, "data"> & { + type: string + data: DeepMutable +} +// kilocode_change end type ProjectorFunc = (db: Database.TxOrDb, data: unknown, event: Event) => void type ConvertEvent = (type: string, data: Event["data"]) => unknown | Promise @@ -70,6 +83,7 @@ export class Service extends Context.Service()("@opencode/Sy export const layer = Layer.effect(Service)( Effect.gen(function* () { const flags = yield* RuntimeFlags.Service + const bus = yield* ProjectBus.Service const replay: Interface["replay"] = Effect.fn("SyncEvent.replay")(function* (event, options) { const def = registry.get(event.type) @@ -106,12 +120,20 @@ export const layer = Layer.effect(Service)( workspace: yield* InstanceState.workspaceID, } : undefined - process(def, event, { - publish, - context, - ownerID: options?.ownerID, - experimentalWorkspaces: flags.experimentalWorkspaces, - }) + // kilocode_change start - decode only EventV2 rows + const data = def.wire ? EventWire.decode(def.schema, event.data) : event.data + process( + def, + { ...event, data }, + { + bus, + publish, + context, + ownerID: options?.ownerID, + experimentalWorkspaces: flags.experimentalWorkspaces, + }, + ) + // kilocode_change end }) const replayAll: Interface["replayAll"] = Effect.fn("SyncEvent.replayAll")(function* (events, options) { @@ -167,7 +189,7 @@ export const layer = Layer.effect(Service)( const seq = row?.seq != null ? row.seq + 1 : 0 const event = { id, seq, aggregateID: agg, data } - process(def, event, { publish, context, experimentalWorkspaces: flags.experimentalWorkspaces }) + process(def, event, { bus, publish, context, experimentalWorkspaces: flags.experimentalWorkspaces }) }, { behavior: "immediate", @@ -204,12 +226,12 @@ export const layer = Layer.effect(Service)( }), ) -export const defaultLayer = layer.pipe(Layer.provide(RuntimeFlags.defaultLayer)) +export const defaultLayer = layer.pipe(Layer.provide([ProjectBus.defaultLayer, RuntimeFlags.defaultLayer])) export const use = serviceUse(Service) export const registry = new Map() -let projectors: Map | undefined +let projectors: Map | undefined const versions = new Map() let frozen = false let convertEvent: ConvertEvent @@ -221,7 +243,18 @@ export function reset() { } export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; convertEvent?: ConvertEvent }) { - projectors = new Map(input.projectors) + projectors = new Map(input.projectors.map(([def, func]) => [versionedType(def.type, def.version), func])) + for (let entry of EventV2.registry.values()) { + if (!entry.version || !entry.aggregate) continue + register({ + type: entry.type, + version: entry.version, + aggregate: entry.aggregate, + properties: entry.data, + schema: entry.data, + wire: true, // kilocode_change + }) + } // Install all the latest event defs to the bus. We only ever emit // latest versions from code, and keep around old versions for @@ -229,7 +262,6 @@ export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; co // simplifies the bus to only use unversioned latest events for (let [type, version] of versions.entries()) { let def = registry.get(versionedType(type, version))! - BusEvent.define(def.type, def.properties) } @@ -269,9 +301,7 @@ export function define< properties: (input.busSchema ?? input.schema) as BusSchema, } - versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0)) - - registry.set(versionedType(def.type, def.version), def) + register(def) return def } @@ -283,22 +313,35 @@ export function project( return [def, func as ProjectorFunc] } +function register(def: Definition) { + versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0)) + registry.set(versionedType(def.type, def.version), def) +} + function process( def: Def, event: Event, - options: { publish: boolean; context?: PublishContext; ownerID?: string; experimentalWorkspaces: boolean }, + options: { + bus: ProjectBus.Interface + publish: boolean + context?: PublishContext + ownerID?: string + experimentalWorkspaces: boolean + }, ) { if (projectors == null) { throw new Error("No projectors available. Call `SyncEvent.init` to install projectors") } - const projector = projectors.get(def) + const projector = projectors.get(versionedType(def.type, def.version)) if (!projector) { - throw new Error(`Projector not found for event: ${def.type}`) + if (!def.type.includes("next")) throw new Error(`Projector not found for event: ${def.type}`) + return } Database.transaction((tx) => { projector(tx, event.data, event) + const data = def.wire ? EventWire.encode(def.schema, event.data) : event.data // kilocode_change if (options.experimentalWorkspaces) { tx.insert(EventSequenceTable) @@ -318,7 +361,7 @@ function process( seq: event.seq, aggregate_id: event.aggregateID, type: versionedType(def.type, def.version), - data: event.data as Record, + data: data as Record, // kilocode_change }) .run() } @@ -330,7 +373,29 @@ function process( } const result = convertEvent(def.type, event.data) - const publish = (data: unknown) => ProjectBus.publish(def, data as Properties, { id: event.id }) + // kilocode_change start - encode EventV2 properties before crossing the legacy boundary + const publish = (value: unknown) => { + const refs = { + instance: options.context?.instance, + workspace: options.context?.workspace, + } + if (def.wire) { + return Effect.runPromise( + attachWith( + options.bus.publish( + { type: def.type, properties: EffectSchema.toEncoded(def.properties) }, + EventWire.encode(def.properties, value), + { id: event.id }, + ), + refs, + ), + ) + } + return Effect.runPromise( + attachWith(options.bus.publish(def, value as Properties, { id: event.id }), refs), + ) + } + // kilocode_change end if (result instanceof Promise) { void result.then(publish) } else { @@ -346,6 +411,7 @@ function process( syncEvent: { type: versionedType(def.type, def.version), ...event, + data, // kilocode_change }, }, }) @@ -355,19 +421,38 @@ function process( } export function effectPayloads() { - return registry - .entries() - .map(([type, def]) => - EffectSchema.Struct({ - type: EffectSchema.Literal("sync"), - name: EffectSchema.Literal(type), - id: EffectSchema.String, - seq: EffectSchema.Finite, - aggregateID: EffectSchema.Literal(def.aggregate), - data: def.schema, - }).annotate({ identifier: `SyncEvent.${type}` }), - ) - .toArray() + return [ + ...registry + .entries() + .map(([type, def]) => + EffectSchema.Struct({ + type: EffectSchema.Literal("sync"), + name: EffectSchema.Literal(type), + id: EffectSchema.String, + seq: EffectSchema.Finite, + aggregateID: EffectSchema.Literal(def.aggregate), + data: def.schema, + }).annotate({ identifier: `SyncEvent.${type}` }), + ) + .toArray(), + ...EventV2.registry + .values() + .filter( + (definition) => + definition.version !== undefined && !registry.has(versionedType(definition.type, definition.version)), + ) + .map((definition) => + EffectSchema.Struct({ + type: EffectSchema.Literal("sync"), + name: EffectSchema.Literal(versionedType(definition.type, definition.version!)), + id: EffectSchema.String, + seq: EffectSchema.Finite, + aggregateID: EffectSchema.Literal(definition.aggregate!), + data: definition.data, + }).annotate({ identifier: `SyncEvent.${definition.type}` }), + ) + .toArray(), + ] } export * as SyncEvent from "." diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index 264a2ee786..c821bb615b 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -3,7 +3,7 @@ import { Effect, Schema } from "effect" import { EffectBridge } from "../effect/bridge" import * as Tool from "./tool" import { Git } from "../git" -import { Instance } from "../project/instance" +import { Instance } from "../kilocode/instance" import { Locale } from "../util/locale" import { Filesystem } from "../util/filesystem" // kilocode_change import { WorktreeFamily } from "../kilocode/worktree-family" // kilocode_change diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index bc9639ecff..520f01754d 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -1,10 +1,8 @@ import { PlanExitTool } from "./plan" import { Session } from "@/session/session" import { QuestionTool } from "./question" -// kilocode_change start import { SuggestTool } from "../kilocode/suggestion/tool" import { Command } from "@/command" -// kilocode_change end import { ShellTool } from "./shell" import { EditTool } from "./edit" import { GlobTool } from "./glob" @@ -27,10 +25,10 @@ import { Plugin } from "../plugin" import { Provider } from "@/provider/provider" import { ProviderID, type ModelID } from "../provider/schema" import { WebSearchTool } from "./websearch" -import { KiloToolRegistry } from "../kilocode/tool/registry" // kilocode_change +import { KiloToolRegistry } from "../kilocode/tool/registry" import { RepoCloneTool } from "./repo_clone" import { RepoOverviewTool } from "./repo_overview" -import { Flag } from "@opencode-ai/core/flag/flag" // kilocode_change +import { Flag } from "@opencode-ai/core/flag/flag" import * as Log from "@opencode-ai/core/util/log" import { LspTool } from "./lsp" import * as Truncate from "./truncate" @@ -55,7 +53,7 @@ import { Agent } from "../agent/agent" import { Git } from "@/git" import { Skill } from "../skill" import { Permission } from "@/permission" -import { SessionStatus } from "@/session/status" // kilocode_change +import { SessionStatus } from "@/session/status" import { Reference } from "@/reference/reference" import { BackgroundJob } from "@/background/job" import { RuntimeFlags } from "@/effect/runtime-flags" @@ -66,7 +64,7 @@ export function webSearchEnabled( providerID: ProviderID, flags = { exa: Flag.KILO_ENABLE_EXA, parallel: Flag.KILO_ENABLE_PARALLEL }, ) { - return providerID === ProviderID.kilo || flags.exa || flags.parallel // kilocode_change + return providerID === ProviderID.kilo || flags.exa || flags.parallel } type TaskDef = Tool.InferDef @@ -112,9 +110,7 @@ export const layer: Layer.Layer< | Ripgrep.Service | Format.Service | Truncate.Service - // kilocode_change start | Command.Service - // kilocode_change end | RuntimeFlags.Service > = Layer.effect( Service, @@ -146,10 +142,8 @@ export const layer: Layer.Layer< const patchtool = yield* ApplyPatchTool const skilltool = yield* SkillTool const agent = yield* Agent.Service - // kilocode_change start const suggesttool = yield* SuggestTool const kiloToolInfos = yield* KiloToolRegistry.infos() - // kilocode_change end const state = yield* InstanceState.make( Effect.fn("ToolRegistry.state")(function* (ctx) { @@ -218,7 +212,8 @@ export const layer: Layer.Layer< // `match` is an absolute filesystem path from `Glob.scanSync(..., { absolute: true })`. // Import it as `file://` so Node on Windows accepts the dynamic import. const mod = yield* Effect.promise(() => import(pathToFileURL(match).href)) - for (const [id, def] of Object.entries(mod)) { + for (const [id, def] of Object.entries(mod)) { + if (!isPluginTool(def)) continue custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) } } @@ -230,8 +225,8 @@ export const layer: Layer.Layer< } } - const cfg = yield* config.get() // kilocode_change: capture for KiloToolRegistry.extra - const questionEnabled = ["app", "cli", "desktop", "vscode"].includes(flags.client) || flags.enableQuestionTool // kilocode_change: add vscode client + const cfg = yield* config.get() + const questionEnabled = ["app", "cli", "desktop", "vscode"].includes(flags.client) || flags.enableQuestionTool const tool = yield* Effect.all({ invalid: Tool.init(invalid), @@ -253,14 +248,13 @@ export const layer: Layer.Layer< question: Tool.init(question), lsp: Tool.init(lsptool), plan: Tool.init(plan), - suggest: Tool.init(suggesttool), // kilocode_change + suggest: Tool.init(suggesttool), }) - const kilo = yield* KiloToolRegistry.build(kiloToolInfos, { agent: agents, truncate }) // kilocode_change + const kilo = yield* KiloToolRegistry.build(kiloToolInfos, { agent: agents, truncate }) return { custom, - // kilocode_change start builtin: KiloToolRegistry.describe( [ tool.invalid, @@ -286,7 +280,6 @@ export const layer: Layer.Layer< ], kilo, ), - // kilocode_change end task: tool.task, read: tool.read, } @@ -343,12 +336,10 @@ export const layer: Layer.Layer< } const usePatch = - // kilocode_change start !!process.env["KILO_E2E_LLM_URL"] || (input.modelID.includes("gpt-") && !input.modelID.includes("oss") && !input.modelID.includes("gpt-4")) - // kilocode_change end if (tool.id === ApplyPatchTool.id) return usePatch - if (tool.id === EditTool.id) return !usePatch // kilocode_change + if (tool.id === EditTool.id) return !usePatch return true }) @@ -420,15 +411,17 @@ export const defaultLayer = Layer.suspend( Layer.provide(Ripgrep.defaultLayer), Layer.provide(Truncate.defaultLayer), ) - // kilocode_change start - provide Kilo-owned registry dependencies .pipe(Layer.provide(Command.defaultLayer), Layer.provide(RuntimeFlags.defaultLayer)), - // kilocode_change end ) function isZodType(value: unknown): value is z.ZodType { return typeof value === "object" && value !== null && "_zod" in value } +function isPluginTool(value: unknown): value is ToolDefinition { + return typeof value === "object" && value !== null && "args" in value && "description" in value && "execute" in value +} + function isJsonSchemaDefinition(value: unknown): value is JSONSchema7Definition { return typeof value === "boolean" || (typeof value === "object" && value !== null && !Array.isArray(value)) } @@ -445,7 +438,7 @@ function legacyJsonSchema(entries: [string, unknown][]): JSONSchema7 { } function zodJsonSchema(schema: z.ZodType): JSONSchema7 { - const result = normalizeZodJsonSchema(z.toJSONSchema(schema, { io: "input" })) + const result = normalizeZodJsonSchema(z.toJSONSchema(schema, { io: "input", metadata: zodMetadataRegistry(schema) })) if (!isJsonSchemaObject(result)) throw new Error("plugin tool Zod schema produced a non-object JSON Schema") const { $defs, ...rest } = result return ( @@ -453,6 +446,32 @@ function zodJsonSchema(schema: z.ZodType): JSONSchema7 { ) as JSONSchema7 } +function zodMetadataRegistry(schema: z.ZodType) { + const registry = z.registry>() + const seen = new WeakSet() + const collect = (value: unknown) => { + if (typeof value !== "object" || value === null) return + if (seen.has(value)) return + seen.add(value) + + if (isZodType(value)) { + const metadata = typeof value.meta === "function" ? value.meta() : undefined + const description = typeof value.description === "string" ? value.description : undefined + const merged = { + ...(metadata && typeof metadata === "object" ? metadata : {}), + ...(description ? { description } : {}), + } + if (Object.keys(merged).length) registry.add(value, merged) + collect(value._zod.def) + return + } + + for (const item of Object.values(value)) collect(item) + } + collect(schema) + return registry +} + function normalizeZodJsonSchema(value: unknown): unknown { if (Array.isArray(value)) return value.map((item) => normalizeZodJsonSchema(item)) if (typeof value !== "object" || value === null) return value diff --git a/packages/opencode/src/tool/repo_overview.ts b/packages/opencode/src/tool/repo_overview.ts index b08516d2c6..e8fd0b81eb 100644 --- a/packages/opencode/src/tool/repo_overview.ts +++ b/packages/opencode/src/tool/repo_overview.ts @@ -6,7 +6,7 @@ import { assertExternalDirectoryEffect } from "./external-directory" import DESCRIPTION from "./repo_overview.txt" import * as Tool from "./tool" import { parseRepositoryReference, repositoryCachePath } from "@/util/repository" -import { Instance } from "@/project/instance" +import { InstanceState } from "@/effect/instance-state" export const Parameters = Schema.Struct({ repository: Schema.optional(Schema.String).annotate({ @@ -108,7 +108,9 @@ export const RepoOverviewTool = Tool.define, ) { if (params.path) { - const full = path.isAbsolute(params.path) ? params.path : path.resolve(Instance.directory, params.path) + const full = path.isAbsolute(params.path) + ? params.path + : path.resolve(yield* InstanceState.directory, params.path) return { path: full, repository: params.repository } } diff --git a/packages/opencode/src/tool/shell.ts b/packages/opencode/src/tool/shell.ts index 322244a444..c81a5943a4 100644 --- a/packages/opencode/src/tool/shell.ts +++ b/packages/opencode/src/tool/shell.ts @@ -649,10 +649,10 @@ export const ShellTool = Tool.define( parameters: prompt.parameters, execute: (params: Parameters, ctx: Tool.Context) => Effect.gen(function* () { - const executeInstance = yield* InstanceState.context + const instanceCtx = yield* InstanceState.context const cwd = params.workdir - ? yield* resolvePath(params.workdir, executeInstance.directory, shell) - : executeInstance.directory + ? yield* resolvePath(params.workdir, instanceCtx.directory, shell) + : instanceCtx.directory if (params.timeout !== undefined && params.timeout < 0) { throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`) } @@ -663,9 +663,9 @@ export const ShellTool = Tool.define( const tree = yield* Effect.acquireRelease(parse(params.command, ps), (tree) => Effect.sync(() => tree.delete()), ) - const scan = yield* collect(tree.rootNode, cwd, ps, shell, executeInstance) + const scan = yield* collect(tree.rootNode, cwd, ps, shell, instanceCtx) // kilocode_change start - if (!containsPath(cwd, executeInstance)) { + if (!containsPath(cwd, instanceCtx)) { scan.dirs.add(cwd) scan.access = "unknown" } diff --git a/packages/opencode/src/tool/shell/shell.txt b/packages/opencode/src/tool/shell/shell.txt index 81d6f337e4..23f228bdb2 100644 --- a/packages/opencode/src/tool/shell/shell.txt +++ b/packages/opencode/src/tool/shell/shell.txt @@ -12,68 +12,12 @@ IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO N ${commandSection} -# Committing changes with git - -Only create commits when requested by the user. If unclear, ask first. When the user asks you to create a new git commit, follow these steps carefully: - -Git Safety Protocol: -- NEVER update the git config -- NEVER run destructive/irreversible git commands (like push --force, hard reset, etc) unless the user explicitly requests them -- NEVER skip hooks (--no-verify, --no-gpg-sign, etc) unless the user explicitly requests it -- NEVER run force push to main/master, warn the user if they request it -- Avoid git commit --amend. ONLY use --amend when ALL conditions are met: - (1) User explicitly requested amend, OR the commit succeeded and pre-commit hooks auto-modified files that need including — verify by checking `git log` that HEAD is the new commit before amending - (2) HEAD commit was created by you in this conversation (verify: git log -1 --format='%an %ae') - (3) Commit has NOT been pushed to remote (verify: git status shows "Your branch is ahead") -- CRITICAL: If commit FAILED or was REJECTED by hook, NEVER amend - fix the issue and create a NEW commit -- CRITICAL: If you already pushed to remote, NEVER amend unless user explicitly requests it (requires force push) -- NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive. - -1. You can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. run the following ${gitCommands} in parallel, each using the ${toolName} tool: - - Run a git status command to see all untracked files. - - Run a git diff command to see both staged and unstaged changes that will be committed. - - Run a git log command to see recent commit messages, so that you can follow this repository's commit message style. -2. Analyze all staged changes (both previously staged and newly added) and draft a commit message: - - Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.). Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.). - - Do not commit files that likely contain secrets (.env, credentials.json, etc.). Warn the user if they specifically request to commit those files - - Draft a concise (1-2 sentences) commit message that focuses on the "why" rather than the "what" - - Ensure it accurately reflects the changes and their purpose -3. You can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. run the following commands: - - Add relevant untracked files to the staging area. - - Create the commit with a message - - Run git status after the commit completes to verify success. - Note: git status depends on the commit completing, so run it sequentially after the commit. -4. If the commit fails due to pre-commit hook, fix the issue and create a NEW commit (see amend rules above) - -Important notes: -- NEVER run additional commands to read or explore code, besides ${gitCommandRestriction} -- NEVER use the TodoWrite or Task tools -- DO NOT push to the remote repository unless the user explicitly asks you to do so -- IMPORTANT: Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported. -- If there are no changes to commit (i.e., no untracked files and no modifications), do not create an empty commit - -# Creating pull requests -Use the gh command via the ${toolName} tool for ALL GitHub-related tasks including working with issues, pull requests, checks, and releases. If given a GitHub URL use the gh command to get the information needed. - -IMPORTANT: When the user asks you to create a pull request, follow these steps carefully: - -1. You can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. run the following ${gitCommands} in parallel using the ${toolName} tool, in order to understand the current state of the branch since it diverged from the main branch: - - Run a git status command to see all untracked files - - Run a git diff command to see both staged and unstaged changes that will be committed - - Check if the current branch tracks a remote branch and is up to date with the remote, so you know if you need to push to the remote - - Run a git log command and `git diff [base-branch]...HEAD` to understand the full commit history for the current branch (from the time it diverged from the base branch) -2. Analyze all changes that will be included in the pull request, making sure to look at all relevant commits (NOT just the latest commit, but ALL commits that will be included in the pull request!!!), and draft a pull request summary -3. You can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. run the following commands in parallel: - - Create new branch if needed - - Push to remote with -u flag if needed - - ${createPrInstruction} - -${createPrExample} - - -Important: -- DO NOT use the TodoWrite or Task tools -- Return the PR URL when you're done, so the user can see it - -# Other common operations -- View comments on a GitHub PR: gh api repos/foo/bar/pulls/123/comments +# Git and GitHub +- Only commit, amend, push, or create PRs when explicitly requested. +- Before committing, inspect `git status`, `git diff`, and `git log --oneline -10`; stage only intended files and never commit secrets. +- Write a concise commit message that matches the repo style. +- Do not update git config, skip hooks, use interactive `-i`, force-push, or create empty commits unless explicitly requested. +- If a commit fails or hooks reject it, fix the issue and create a new commit; do not amend the failed commit. +- Before creating a PR, inspect status, diff, remote tracking, recent commits, and the diff from the base branch. +- Review all commits included in the PR, not just the latest commit. +- Use `gh` for GitHub tasks, including PRs, issues, checks, and releases; return the PR URL when done. diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 27b39c7318..ebcb417930 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -30,6 +30,14 @@ export interface TaskPromptOps { } const id = "task" +const BACKGROUND_DESCRIPTION = [ + "", + "", + [ + "Background mode: background=true launches the subagent asynchronously.", + "Use task_status(task_id=..., wait=false) to poll, or wait=true to block until done.", + ].join(" "), +].join("\n") const BaseParameters = Schema.Struct({ description: Schema.String.annotate({ description: "A short (3-5 words) description of the task" }), @@ -137,7 +145,9 @@ export const TaskTool = Tool.define( if (!next) { return yield* Effect.fail(new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`)) } + // kilocode_change start — reject primary agents; only subagent/all modes allowed KiloTask.validate(next, params.subagent_type) + // kilocode_change end const canTask = KiloTask.nestedTask() // kilocode_change - Kilo disallows subagents spawning subagents const canTodo = next.permission.some((rule) => rule.permission === "todowrite") @@ -370,10 +380,12 @@ export const TaskTool = Tool.define( } return yield* Effect.acquireUseRelease( + // kilocode_change start - snapshot child cost so we propagate only the delta on resume (#6321) Effect.gen(function* () { ctx.abort.addEventListener("abort", onAbort) return yield* KiloCostPropagation.childCost(sessions, nextSession.id) }), + // kilocode_change end () => Effect.gen(function* () { const text = yield* runTask() @@ -394,9 +406,12 @@ export const TaskTool = Tool.define( const costAfter = yield* KiloCostPropagation.childCost(sessions, nextSession.id).pipe( Effect.catchTag("NotFoundError", () => Effect.succeed(costBefore)), ) - yield* KiloCostPropagation.propagate(sessions, ctx.sessionID, ctx.messageID, costAfter - costBefore).pipe( - Effect.catchTag("NotFoundError", () => Effect.void), - ) + yield* KiloCostPropagation.propagate( + sessions, + ctx.sessionID, + ctx.messageID, + costAfter - costBefore, + ).pipe(Effect.catchTag("NotFoundError", () => Effect.void)) }), ), ), @@ -405,7 +420,7 @@ export const TaskTool = Tool.define( }) return { - description: DESCRIPTION, + description: flags.experimentalBackgroundSubagents ? DESCRIPTION + BACKGROUND_DESCRIPTION : DESCRIPTION, parameters: Parameters, jsonSchema: flags.experimentalBackgroundSubagents ? undefined : ToolJsonSchema.fromSchema(BaseParameters), execute: (params: Schema.Schema.Type, ctx: Tool.Context) => diff --git a/packages/opencode/src/tool/task.txt b/packages/opencode/src/tool/task.txt index cab2374a3e..e2ac605005 100644 --- a/packages/opencode/src/tool/task.txt +++ b/packages/opencode/src/tool/task.txt @@ -2,57 +2,17 @@ Launch a new agent to handle complex, multistep tasks autonomously. When using the Task tool, you must specify a subagent_type parameter to select which agent type to use. -When to use the Task tool: -- When you are instructed to execute custom slash commands. Use the Task tool with the slash command invocation as the entire prompt. The slash command can take arguments. For example: Task(description="Check the file", prompt="/check-file path/to/file.py") - When NOT to use the Task tool: - If you want to read a specific file path, use the Read or Glob tool instead of the Task tool, to find the match more quickly -- If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly +- If you are searching for a specific class definition like "class Foo", use the Grep tool instead, to find the match more quickly - If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly -- Other tasks that are not related to the agent descriptions above +- If no available agent is a good fit for the task, use other tools directly 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. The output includes a task_id you can reuse later to continue the same subagent session. -3. If OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS is enabled, background=true launches the subagent asynchronously. Use task_status(task_id=..., wait=false) to poll, or wait=true to block until done. -4. 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. -5. The agent's outputs should generally be trusted -6. 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). -7. 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): - - -"code-reviewer": use this agent after you are done writing a significant piece of code -"greeting-responder": use this agent when to respond to user greetings with a friendly joke - - - -user: "Please write a function that checks if a number is prime" -assistant: Sure let me write a function that checks if a number is prime -assistant: First let me use the Write tool to write a function that checks if a number is prime -assistant: I'm going to use the Write tool to write the following code: - -function isPrime(n) { - if (n <= 1) return false - for (let i = 2; i * i <= n; i++) { - if (n % i === 0) return false - } - return true -} - - -Since a significant piece of code was written and the task was completed, now use the code-reviewer agent to review the code - -assistant: Now let me use the code-reviewer agent to review the code -assistant: Uses the Task tool to launch the code-reviewer agent - - - -user: "Hello" - -Since the user is greeting, use the greeting-responder agent to respond with a friendly joke - -assistant: "I'm going to use the Task tool to launch the with the greeting-responder agent" - +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. 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. diff --git a/packages/opencode/src/tool/todowrite.txt b/packages/opencode/src/tool/todowrite.txt index 2737cd18b6..a21630c158 100644 --- a/packages/opencode/src/tool/todowrite.txt +++ b/packages/opencode/src/tool/todowrite.txt @@ -1,167 +1,44 @@ -Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. -It also helps the user understand the progress of the task and overall progress of their requests. - -## When to Use This Tool -Use this tool proactively in these scenarios: - -1. Complex multistep tasks - When a task requires 3 or more distinct steps or actions -2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations -3. User explicitly requests todo list - When the user directly asks you to use the todo list -4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated) -5. After receiving new instructions - Immediately capture user requirements as todos. Feel free to edit the todo list based on new information. -6. After completing a task - Mark it complete and add any new follow-up tasks -7. When you start working on a new task, mark the todo as in_progress. Ideally you should only have one todo as in_progress at a time. Complete existing tasks before starting new ones. - -## When NOT to Use This Tool - -Skip using this tool when: -1. There is only a single, straightforward task -2. The task is trivial and tracking it provides no organizational benefit -3. The task can be completed in less than 3 trivial steps -4. The task is purely conversational or informational - -NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly. - -## Examples of When to Use the Todo List - - -User: I want to add a dark mode toggle to the application settings. Make sure you run the tests and build when you're done! -Assistant: I'll help add a dark mode toggle to your application settings. Let me create a todo list to track this implementation. -*Creates todo list with the following items:* -1. Create dark mode toggle component in Settings page -2. Add dark mode state management (context/store) -3. Implement CSS-in-JS styles for dark theme -4. Update existing components to support theme switching -5. Run tests and build process, addressing any failures or errors that occur -*Begins working on the first task* - - -The assistant used the todo list because: -1. Adding dark mode is a multi-step feature requiring UI, state management, and styling changes -2. The user explicitly requested tests and build be run afterward -3. The assistant inferred that tests and build need to pass by adding "Ensure tests and build succeed" as the final task - - - - -User: Help me rename the function getCwd to getCurrentWorkingDirectory across my project -Assistant: Let me first search through your codebase to find all occurrences of 'getCwd'. -*Uses grep or search tools to locate all instances of getCwd in the codebase* -Assistant: I've found 15 instances of 'getCwd' across 8 different files. Let me create a todo list to track these changes. -*Creates todo list with specific items for each file that needs updating* - - -The assistant used the todo list because: -1. First, the assistant searched to understand the scope of the task -2. Upon finding multiple occurrences across different files, it determined this was a complex task with multiple steps -3. The todo list helps ensure every instance is tracked and updated systematically -4. This approach prevents missing any occurrences and maintains code consistency - - - - - -User: I need to implement these features for my e-commerce site: user registration, product catalog, shopping cart, and checkout flow. -Assistant: I'll help implement these features. First, let's add all the features to the todo list. -*Creates a todo list breaking down each feature into specific tasks based on the project architecture* -Assistant: Let's start with user registration. This will involve creating database models, API endpoints, and frontend forms that integrate with your existing authentication system. - - -The assistant used the todo list because: -1. The user provided multiple complex features to implement in a comma separated list -2. The todo list helps organize these large features into manageable tasks -3. This approach allows for tracking progress across the entire implementation - - - - -User: Can you help optimize my React application? It's rendering slowly and has performance issues. -Assistant: I'll help optimize your React application. First, let me examine your codebase to identify potential performance bottlenecks. -*Reviews component structure, render patterns, state management, and data fetching* -Assistant: After analyzing your codebase, I've identified several performance issues. Let me create a todo list to track our optimization efforts. -*Creates todo list with items like: 1) Implement memoization for expensive calculations in ProductList, 2) Add virtualization for long lists in Dashboard, 3) Optimize image loading in Gallery component, 4) Fix state update loops in ShoppingCart, 5) Review bundle size and implement code splitting* -Let's start by implementing memoization for the expensive calculations in your ProductList component. - - -The assistant used the todo list because: -1. First, the assistant examined the codebase to identify specific performance issues -2. Based on this analysis, it identified multiple optimization opportunities -3. Performance optimization is a non-trivial task requiring multiple steps -4. The todo list helps methodically track improvements across different components -5. This systematic approach ensures all performance bottlenecks are addressed - - - -## Examples of When NOT to Use the Todo List - - -User: How do I print 'Hello World' in Python? -Assistant: In Python, you can print "Hello World" with this simple code: - -python -print("Hello World") - -This will output the text "Hello World" to the console when executed. - - -The assistant did not use the todo list because this is a single, trivial task that can be completed in one step. There's no need to track multiple tasks or steps for such a straightforward request. - - - - -User: What does the git status command do? -Assistant: The git status command shows the current state of your working directory and staging area. It displays which changes have been staged, which haven't, and which files aren't being tracked by Git. This is a fundamental command for understanding the current state of your repository before making commits. - - -The assistant did not use the todo list because this is an informational request with no actual coding task to complete. The user is simply asking for an explanation, not for the assistant to perform multiple steps or tasks. - - - - -User: Can you add a comment to the calculateTotal function to explain what it does? -Assistant: Sure, let me add a comment to the calculateTotal function to explain what it does. -* Uses the Edit tool to add a comment to the calculateTotal function * - - -The assistant did not use the todo list because this is a single, straightforward task confined to one location in the code. Adding a comment doesn't require tracking multiple steps or systematic organization. - - - - -User: Run npm install for me and tell me what happens. -Assistant: I'll run the npm install command for you. - -*Executes: npm install* - -The command completed successfully. Here's the output: -[Output of npm install command] - -All dependencies have been installed according to your package.json file. - - -The assistant did not use the todo list because this is a single command execution with immediate results. There are no multiple steps to track or organize, making the todo list unnecessary for this straightforward task. - - - -## Task States and Management - -1. **Task States**: Use these states to track progress: - - pending: Task not yet started - - in_progress: Currently working on (limit to ONE task at a time) - - completed: Task finished successfully - - cancelled: Task no longer needed - -2. **Task Management**: - - Update task status in real-time as you work - - Mark tasks complete IMMEDIATELY after finishing (don't batch completions) - - Only have ONE task in_progress at any time - - Complete current tasks before starting new ones - - Cancel tasks that become irrelevant - -3. **Task Breakdown**: - - Create specific, actionable items - - Break complex tasks into smaller, manageable steps - - Use clear, descriptive task names - -When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully. - +Create and maintain a structured task list for the current coding session. Tracks progress, organizes multi-step work, and surfaces status to the user. + +## When to use +Use proactively when: +- The task requires 3+ distinct steps or actions (not just 3 tool calls for a single conceptual step) +- The work is non-trivial and benefits from planning +- The user provides multiple tasks (numbered or comma-separated) or explicitly asks for a todo list +- New instructions arrive - capture them as todos +- You start a task - mark it `in_progress` (only one at a time) before working +- You finish a task - mark it `completed` and add any follow-ups discovered during the work + +## When NOT to use +Skip when: +- The work is a single, straightforward task (or <3 trivial steps) +- The request is purely informational or conversational +- Tracking adds no organizational value + +## States +- `pending` - not started +- `in_progress` - actively working (exactly ONE at a time) +- `completed` - finished successfully +- `cancelled` - no longer needed + +## Rules +- Update status in real time; don't batch completions +- Mark `completed` only after the required work is actually done, including any required verification. Never based on intent. +- Keep exactly one `in_progress` while work remains +- If blocked or partial, keep it `in_progress` and add a follow-up todo describing the blocker +- Preserve user-provided commands verbatim (flags, args, order) +- Items should be specific and actionable; break large work into smaller steps + +## Examples + +Use it: +- "Add a dark mode toggle and run the tests" -> multi-step feature + explicit verification +- "Rename getCwd -> getCurrentWorkingDirectory across the repo" -> grep reveals 15 occurrences in 8 files +- "Implement registration, catalog, cart, checkout" -> multiple complex features + +Skip it: +- "How do I print Hello World in Python?" -> informational +- "Add a comment to calculateTotal" -> single edit +- "Run npm install and tell me what happened" -> one command + +When in doubt, use it. diff --git a/packages/opencode/src/tool/warpgrep.ts b/packages/opencode/src/tool/warpgrep.ts index 16ffb47415..1336fbddfc 100644 --- a/packages/opencode/src/tool/warpgrep.ts +++ b/packages/opencode/src/tool/warpgrep.ts @@ -2,7 +2,7 @@ import { Effect, Schema } from "effect" import * as Tool from "./tool" import { WarpGrepClient } from "@morphllm/morphsdk/tools/warp-grep/client" // kilocode_change import { Telemetry } from "@kilocode/kilo-telemetry" // kilocode_change -import { Instance } from "../project/instance" +import { Instance } from "../kilocode/instance" // kilocode_change import { Bus } from "../bus" import { TuiEvent } from "../cli/cmd/tui/event" import DESCRIPTION from "./warpgrep.txt" @@ -61,7 +61,7 @@ export const CodebaseSearchTool = Tool.define( "Codebase search unavailable: free period ended. Set MORPH_API_KEY to continue. Get your key at https://www.morphllm.com/" if (isAuthOrRateLimit) { yield* Effect.promise(() => - Bus.publish(TuiEvent.ToastShow, { + Bus.publish(Instance.current, TuiEvent.ToastShow, { // kilocode_change title: "Codebase Search Unavailable", message: "Free period has ended. Set MORPH_API_KEY to continue. Get your key at morphllm.com", variant: "error", diff --git a/packages/opencode/src/util/archive.ts b/packages/opencode/src/util/archive.ts index 8fc5497793..2d48a8d4f5 100644 --- a/packages/opencode/src/util/archive.ts +++ b/packages/opencode/src/util/archive.ts @@ -6,8 +6,16 @@ export async function extractZip(zipPath: string, destDir: string) { const winZipPath = path.resolve(zipPath) const winDestDir = path.resolve(destDir) // $global:ProgressPreference suppresses PowerShell's blue progress bar popup - const cmd = `$global:ProgressPreference = 'SilentlyContinue'; Expand-Archive -Path '${winZipPath}' -DestinationPath '${winDestDir}' -Force` - await Process.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", cmd]) + // kilocode_change start - keep paths out of the PowerShell program + const cmd = + "$global:ProgressPreference = 'SilentlyContinue'; Expand-Archive -LiteralPath $env:KILO_ARCHIVE_PATH -DestinationPath $env:KILO_ARCHIVE_DESTINATION -Force" + await Process.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", cmd], { + env: { + KILO_ARCHIVE_PATH: winZipPath, + KILO_ARCHIVE_DESTINATION: winDestDir, + }, + }) + // kilocode_change end return } diff --git a/packages/opencode/src/v2/session.ts b/packages/opencode/src/v2/session.ts index 97c31d39b2..a3c386f66d 100644 --- a/packages/opencode/src/v2/session.ts +++ b/packages/opencode/src/v2/session.ts @@ -4,14 +4,14 @@ import { WorkspaceID } from "@/control-plane/schema" import { and, asc, desc, eq, gt, gte, isNull, like, lt, or, type SQL } from "@/storage/db" import * as Database from "@/storage/db" import { Context, DateTime, Effect, Layer, Option, Schema } from "effect" -import { SessionMessage } from "./session-message" +import { SessionMessage } from "@opencode-ai/core/session-message" import type { Prompt } from "@opencode-ai/core/session-prompt" -import { EventV2 } from "./event" import { ProjectID } from "@/project/schema" -import { SessionEvent } from "./session-event" +import { SessionEvent } from "@opencode-ai/core/session-event" import { V2Schema } from "@opencode-ai/core/v2-schema" import { optionalOmitUndefined } from "@opencode-ai/core/schema" -import { SyncEvent } from "@/sync" +import { EventV2 } from "@opencode-ai/core/event" +import { EventV2Bridge } from "@/event-v2-bridge" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" @@ -125,7 +125,7 @@ export class Service extends Context.Service()("@opencode/v2 export const layer = Layer.effect( Service, Effect.gen(function* () { - const sync = yield* SyncEvent.Service + const events = yield* EventV2Bridge.Service const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message) const decode = (row: typeof SessionMessageTable.$inferSelect) => @@ -292,14 +292,14 @@ export const layer = Layer.effect( shell: Effect.fn("V2Session.shell")(function* (_input) {}), skill: Effect.fn("V2Session.skill")(function* (_input) {}), switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) { - yield* sync.run(SessionEvent.AgentSwitched.Sync, { + yield* events.publish(SessionEvent.AgentSwitched, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), agent: input.agent, }) }), switchModel: Effect.fn("V2Session.switchModel")(function* (input) { - yield* sync.run(SessionEvent.ModelSwitched.Sync, { + yield* events.publish(SessionEvent.ModelSwitched, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), model: input.model, @@ -334,6 +334,6 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(SyncEvent.defaultLayer)) +export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer)) export * as SessionV2 from "./session" diff --git a/packages/opencode/test/AGENTS.md b/packages/opencode/test/AGENTS.md index bff1ff5dde..464b75cd82 100644 --- a/packages/opencode/test/AGENTS.md +++ b/packages/opencode/test/AGENTS.md @@ -116,7 +116,7 @@ describe("my service", () => { Prefer the Effect-aware helpers from `fixture/fixture.ts` instead of building a manual runtime in each test. - `tmpdirScoped(options?)` creates a scoped temp directory and cleans it up when the Effect scope closes. -- `provideInstance(dir)(effect)` is the low-level helper. It does not create a directory; it just runs an Effect with `Instance.current` bound to `dir`. +- `provideInstance(dir)(effect)` is the low-level helper. It does not create a directory; it runs an Effect with `InstanceRef` provided for `dir`. - `provideTmpdirInstance((dir) => effect, options?)` is the convenience helper. It creates a temp directory, binds it as the active instance, and disposes the instance on cleanup. - `provideTmpdirServer((input) => effect, options?)` does the same, but also provides the test LLM server. diff --git a/packages/opencode/test/EFFECT_TEST_MIGRATION.md b/packages/opencode/test/EFFECT_TEST_MIGRATION.md index 73acea9148..893aa922b7 100644 --- a/packages/opencode/test/EFFECT_TEST_MIGRATION.md +++ b/packages/opencode/test/EFFECT_TEST_MIGRATION.md @@ -149,7 +149,7 @@ Do not maintain a long file checklist here. It goes stale quickly. When looking for the next target, search for current anti-patterns: ```bash -git grep -n "Effect.runPromise\|ManagedRuntime\|Promise.withResolvers\|Bun.sleep\|WithInstance" -- packages/opencode/test +git grep -n "Effect.runPromise\|ManagedRuntime\|Promise.withResolvers\|Bun.sleep\|withTestInstance" -- packages/opencode/test ``` Then choose one file or one small cluster, keep the PR focused, and mention diff --git a/packages/opencode/test/cli/cmd/tui/aggregate-failures.test.ts b/packages/opencode/test/cli/cmd/tui/aggregate-failures.test.ts index c9b3551d9a..c30d719252 100644 --- a/packages/opencode/test/cli/cmd/tui/aggregate-failures.test.ts +++ b/packages/opencode/test/cli/cmd/tui/aggregate-failures.test.ts @@ -5,6 +5,7 @@ */ import { describe, expect, test } from "bun:test" import { aggregateFailures } from "@/cli/cmd/tui/context/aggregate-failures" +import { ConfigError } from "@/config/error" describe("aggregateFailures", () => { test("returns null when every result is fulfilled", () => { @@ -41,11 +42,48 @@ describe("aggregateFailures", () => { expect(err!.message).toContain("agents: boom") }) + test("formats structured config errors hidden inside SDK error causes", () => { + const configError = new ConfigError.InvalidError({ + path: "/tmp/opencode.json", + issues: [{ message: "Expected object", path: ["provider", "anthropic", "options"] }], + }) + const err = aggregateFailures([ + { + name: "config.get", + result: { + status: "rejected", + reason: new Error("ConfigInvalidError", { + cause: { + body: configError.toObject(), + }, + }), + }, + }, + ]) + + expect(err!.message).toContain("config.get: Configuration is invalid at /tmp/opencode.json") + expect(err!.message).toContain("Expected object provider.anthropic.options") + }) + + test("deduplicates identical failure messages across startup requests", () => { + const reason = new Error("same config problem") + const err = aggregateFailures([ + { name: "config.providers", result: { status: "rejected", reason } }, + { name: "provider.list", result: { status: "rejected", reason } }, + { name: "app.agents", result: { status: "rejected", reason } }, + { name: "config.get", result: { status: "rejected", reason } }, + { name: "project.sync", result: { status: "fulfilled", value: undefined } }, + ]) + + expect(err!.message).toContain("4 of 5 requests failed: same config problem") + expect(err!.message).toContain("Affected startup requests: config.providers, provider.list, app.agents, config.get") + expect(err!.message.match(/same config problem/g)?.length).toBe(1) + }) + test("attaches structured failure list under .cause", () => { const reason = new Error("nope") const err = aggregateFailures([{ name: "providers", result: { status: "rejected", reason } }]) - const cause = err!.cause as { failures: Array<{ name: string; reason: unknown }> } - expect(cause.failures).toEqual([{ name: "providers", reason }]) + expect(err!.cause).toEqual({ failures: [{ name: "providers", reason }] }) }) test("falls back to String() for opaque reasons", () => { diff --git a/packages/opencode/test/cli/cmd/tui/prompt-history.test.ts b/packages/opencode/test/cli/cmd/tui/prompt-history.test.ts new file mode 100644 index 0000000000..f92b34d905 --- /dev/null +++ b/packages/opencode/test/cli/cmd/tui/prompt-history.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from "bun:test" +import { isDuplicateEntry, type PromptInfo } from "../../../../src/cli/cmd/tui/component/prompt/history" + +const entry = (input: string, parts: PromptInfo["parts"] = []): PromptInfo => ({ input, parts }) + +describe("prompt history dedupe", () => { + test("returns false when there is no previous entry", () => { + expect(isDuplicateEntry(undefined, entry("hello"))).toBe(false) + }) + + test("dedupes identical consecutive entries", () => { + const a = entry("hello world this is over twenty chars") + const b = entry("hello world this is over twenty chars") + expect(isDuplicateEntry(a, b)).toBe(true) + }) + + test("does not dedupe when input text differs", () => { + expect(isDuplicateEntry(entry("foo"), entry("bar"))).toBe(false) + }) + + test("does not dedupe when parts differ", () => { + const a = entry("describe this", [ + { + type: "file", + mime: "image/png", + filename: "a.png", + url: "data:image/png;base64,AAA", + }, + ]) + const b = entry("describe this", [ + { + type: "file", + mime: "image/png", + filename: "b.png", + url: "data:image/png;base64,BBB", + }, + ]) + expect(isDuplicateEntry(a, b)).toBe(false) + }) + + test("does not dedupe when mode differs", () => { + expect(isDuplicateEntry({ ...entry("ls"), mode: "normal" }, { ...entry("ls"), mode: "shell" })).toBe(false) + }) +}) diff --git a/packages/opencode/test/cli/cmd/tui/sync-undefined-messages.test.tsx b/packages/opencode/test/cli/cmd/tui/sync-undefined-messages.test.tsx index 108afc162f..910c4fb84c 100644 --- a/packages/opencode/test/cli/cmd/tui/sync-undefined-messages.test.tsx +++ b/packages/opencode/test/cli/cmd/tui/sync-undefined-messages.test.tsx @@ -8,8 +8,7 @@ */ import { describe, expect, test } from "bun:test" import { Global } from "@opencode-ai/core/global" -import { WithInstance } from "../../../../src/project/with-instance" // kilocode_change -import { disposeAllInstances, tmpdir } from "../../../fixture/fixture" +import { disposeAllInstances, provideTestInstance, tmpdir } from "../../../fixture/fixture" import { directory, json, mount } from "./sync-fixture" const sessionID = "ses_undef" @@ -29,7 +28,8 @@ describe("tui sync (#26560)", () => { directory, project_id: "proj_test", } - const { app, sync } = await WithInstance.provide({ + // kilocode_change start + const { app, sync } = await provideTestInstance({ directory: tmp.path, fn: () => mount((url) => { @@ -40,7 +40,8 @@ describe("tui sync (#26560)", () => { if (url.pathname === "/session") return json([sessionPayload]) return undefined }), - }) // kilocode_change + }) + // kilocode_change end try { await expect(sync.session.sync(sessionID)).resolves.toBeUndefined() diff --git a/packages/opencode/test/cli/cmd/tui/sync.test.tsx b/packages/opencode/test/cli/cmd/tui/sync.test.tsx index 72d11db08f..1c8b3d36f5 100644 --- a/packages/opencode/test/cli/cmd/tui/sync.test.tsx +++ b/packages/opencode/test/cli/cmd/tui/sync.test.tsx @@ -1,8 +1,7 @@ /** @jsxImportSource @opentui/solid */ import { describe, expect, test } from "bun:test" import { Global } from "@opencode-ai/core/global" -import { WithInstance } from "../../../../src/project/with-instance" // kilocode_change -import { disposeAllInstances, tmpdir } from "../../../fixture/fixture" +import { disposeAllInstances, provideTestInstance, tmpdir } from "../../../fixture/fixture" import { mount, wait } from "./sync-fixture" import type { GlobalEvent } from "@kilocode/sdk/v2" @@ -25,7 +24,7 @@ describe("tui sync", () => { await using tmp = await tmpdir() Global.Path.state = tmp.path await Bun.write(`${tmp.path}/kv.json`, "{}") - const { app, kv, sync, session } = await WithInstance.provide({ directory: tmp.path, fn: mount }) // kilocode_change + const { app, kv, sync, session } = await provideTestInstance({ directory: tmp.path, fn: () => mount() }) // kilocode_change try { expect(kv.get("session_directory_filter_enabled", true)).toBe(true) diff --git a/packages/opencode/test/cli/effect-cmd-instance-als.test.ts b/packages/opencode/test/cli/effect-cmd-instance-als.test.ts index 122b87f174..e563c1793e 100644 --- a/packages/opencode/test/cli/effect-cmd-instance-als.test.ts +++ b/packages/opencode/test/cli/effect-cmd-instance-als.test.ts @@ -1,9 +1,10 @@ import { afterEach, expect } from "bun:test" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Effect } from "effect" -import { fileURLToPath } from "url" +import { effectCmd } from "../../src/cli/effect-cmd" +import { EffectBridge } from "../../src/effect/bridge" import { InstanceRef } from "../../src/effect/instance-ref" -import { Instance } from "../../src/project/instance" +import { Instance } from "../../src/kilocode/instance" import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" @@ -13,46 +14,54 @@ afterEach(async () => { await disposeAllInstances() }) -// Regression for PR #25522: when an effectCmd handler does -// `yield* Effect.promise(async () => { ... await runPromise(svcMethod) ... })`, -// the inner runPromise creates a fresh fiber after `await` whose Effect context -// has lost the outer InstanceRef. Services that read `InstanceState.context` -// then fall back to `Instance.current` ALS, which must be installed at the JS -// callback boundary (Node ALS persists across awaits, Effect's fiber context -// does not). `it.instance` provides the loaded InstanceRef; the explicit -// Instance.restore mirrors effectCmd's load + ALS-restore wrap. -// Pins effect-cmd.ts directly: the pattern test below exercises the load + -// Instance.restore boundary via the shared `it.instance` fixture, -// so a regression that removed `Instance.restore` from effect-cmd.ts wouldn't -// fail it. This grep guards the actual production callsite. -it.live("effect-cmd.ts wraps the handler body in Instance.restore", () => - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - const source = yield* fs.readFileString(fileURLToPath(new URL("../../src/cli/effect-cmd.ts", import.meta.url))) - expect(source).toContain("Instance.restore(ctx") - }), +it.instance( + "EffectBridge preserves legacy instance context across Promise awaits", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const directory = yield* EffectBridge.fromPromise(async () => { + await Promise.resolve() + return Instance.directory + }) + const bridge = yield* EffectBridge.make() + const bound = bridge.bind(async () => { + await Promise.resolve() + return Instance.directory + }) + + expect(directory).toBe(test.directory) + expect(yield* Effect.promise(bound)).toBe(test.directory) + }), + { git: true }, ) it.instance( - "Instance.current reachable after await inside restored Effect.promise(async)", + "effectCmd preserves Effect and legacy instance contexts across Promise awaits", () => Effect.gen(function* () { const test = yield* TestInstance - const ctx = yield* InstanceRef - if (!ctx) throw new Error("InstanceRef not provided") + let reference: string | undefined + let legacy: string | undefined + const command = effectCmd({ + command: "context-test", + describe: false, + directory: () => test.directory, + handler: () => + Effect.gen(function* () { + reference = (yield* InstanceRef)?.directory + legacy = yield* Effect.promise(async () => { + await Promise.resolve() + return Instance.directory + }) + }), + }) + const handler = command.handler + if (!handler) return yield* Effect.die(new Error("effect command handler not provided")) - const current = yield* Effect.promise(() => - Instance.restore(ctx, async () => { - await Promise.resolve() - try { - return Instance.current - } catch { - return undefined - } - }), - ) + yield* Effect.promise(() => Promise.resolve(handler({} as never))) - expect(current?.directory).toBe(test.directory) + expect(reference).toBe(test.directory) + expect(legacy).toBe(test.directory) }), { git: true }, ) diff --git a/packages/opencode/test/cli/error.test.ts b/packages/opencode/test/cli/error.test.ts index 20d63c64ba..12de6ffbda 100644 --- a/packages/opencode/test/cli/error.test.ts +++ b/packages/opencode/test/cli/error.test.ts @@ -74,7 +74,7 @@ describe("cli.error", () => { "Model not found: anthropic/claude-sonet-4", "Did you mean: claude-sonnet-4", "Try: `kilo models` to list available models", - "Or check your config (opencode.json) provider/model names", + "Or check your kilo.json provider/model names", // kilocode_change ].join("\n") expect(FormatError({ name: "ProviderModelNotFoundError", data })).toBe(expected) diff --git a/packages/opencode/test/cli/install-artifact.test.ts b/packages/opencode/test/cli/install-artifact.test.ts index c15a1a9e3d..933ce0f7c5 100644 --- a/packages/opencode/test/cli/install-artifact.test.ts +++ b/packages/opencode/test/cli/install-artifact.test.ts @@ -35,18 +35,27 @@ describe("npm install artifact behavior", () => { await fs.mkdir(path.join(bin, "tree-sitter"), { recursive: true }) await fs.mkdir(path.join(bin, "console", "assets"), { recursive: true }) await fs.copyFile(postinstall, path.join(pkg, "postinstall.mjs")) + await Bun.write( + path.join(pkg, "package.json"), + JSON.stringify({ + optionalDependencies: { + [`@kilocode/cli-${process.platform}-${process.arch}`]: "1.0.0", + }, + }), + ) await Bun.write( path.join(native, "package.json"), JSON.stringify({ name: `@kilocode/cli-${process.platform}-${process.arch}` }), ) - await Bun.write(path.join(bin, "kilo"), "binary") + const binary = "#!/bin/sh\n# binary\nexit 0\n" + await Bun.write(path.join(bin, "kilo"), binary) await Bun.write(path.join(bin, "tree-sitter", "tree-sitter.wasm"), "wasm") await Bun.write(path.join(bin, "console", "index.html"), "console") await Bun.write(path.join(bin, "console", "assets", "app.js"), "asset") const proc = Bun.spawn([node, path.join(pkg, "postinstall.mjs")], { cwd: pkg }) expect(await proc.exited).toBe(0) - expect(await Bun.file(path.join(pkg, "bin", ".kilo")).text()).toBe("binary") + expect(await Bun.file(path.join(pkg, "bin", ".kilo")).text()).toBe(binary) expect(await Bun.file(path.join(pkg, "bin", "tree-sitter", "tree-sitter.wasm")).text()).toBe("wasm") expect(await Bun.file(path.join(pkg, "bin", "console", "index.html")).text()).toBe("console") expect(await Bun.file(path.join(pkg, "bin", "console", "assets", "app.js")).text()).toBe("asset") diff --git a/packages/opencode/test/cli/run/prompt.shared.test.ts b/packages/opencode/test/cli/run/prompt.shared.test.ts index 299751eaa3..35b35ec3e7 100644 --- a/packages/opencode/test/cli/run/prompt.shared.test.ts +++ b/packages/opencode/test/cli/run/prompt.shared.test.ts @@ -126,6 +126,12 @@ describe("run prompt shared", () => { expect(mentionTriggerIndex("👨‍👩‍👧‍👦 @src", Bun.stringWidth("👨‍👩‍👧‍👦 @src"))).toBe(3) expect(displayCharAt("👨‍👩‍👧‍👦 @src", Bun.stringWidth("👨‍👩‍👧‍👦 @"))).toBe("s") expect(displaySlice("👨‍👩‍👧‍👦 @src", 3, Bun.stringWidth("👨‍👩‍👧‍👦 @src"))).toBe("@src") + expect(mentionTriggerIndex("@file1\n@file2", 13)).toBe(7) + expect(displayCharAt("@file1\n@file2", 6)).toBe("\n") + expect(displaySlice("@file1\n@file2", 8, 13)).toBe("file2") + expect(mentionTriggerIndex("@file1\nfoo @file2", 17)).toBe(11) + expect(mentionTriggerIndex("中文 @one\n@two", 14)).toBe(10) + expect(displaySlice("中文 @one\n@two", 11, 14)).toBe("two") expect(mentionTriggerIndex("中文@")).toBeUndefined() expect(mentionTriggerIndex("こんにちは@")).toBeUndefined() expect(mentionTriggerIndex("한국어@")).toBeUndefined() diff --git a/packages/opencode/test/cli/run/stream.transport.test.ts b/packages/opencode/test/cli/run/stream.transport.test.ts index 1934261374..309222d0f8 100644 --- a/packages/opencode/test/cli/run/stream.transport.test.ts +++ b/packages/opencode/test/cli/run/stream.transport.test.ts @@ -762,9 +762,7 @@ describe("run stream transport", () => { transport.selectSubagent("child-1") - global.push( - globalEvent(assistant("msg-child-1", "child-1")), - ) + global.push(globalEvent(assistant("msg-child-1", "child-1"))) global.push(globalEvent(textUpdated(textPart("txt-child-1", "msg-child-1", "hello", "child-1")))) expect( diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 23b326f228..0a6e0713ff 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -6,14 +6,14 @@ import { ConfigManaged } from "@/config/managed" import { ConfigParse } from "../../src/config/parse" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { InstanceRef } from "../../src/effect/instance-ref" +import type { InstanceContext } from "../../src/project/instance-context" import { Auth } from "../../src/auth" import { Account } from "../../src/account/account" import { AccessToken, AccountID, OrgID } from "../../src/account/schema" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Env } from "../../src/env" -import { provideTestInstance, provideTmpdirInstance } from "../fixture/fixture" +import { provideTestInstance, provideTmpdirInstance, withTestInstance } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture" import { InstanceRuntime } from "@/project/instance-runtime" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" @@ -61,9 +61,20 @@ const layer = Config.layer.pipe( const it = testEffect(layer) -const load = () => Effect.runPromise(Config.Service.use((svc) => svc.get()).pipe(Effect.scoped, Effect.provide(layer))) -const save = (config: Config.Info) => - Effect.runPromise(Config.Service.use((svc) => svc.update(config)).pipe(Effect.scoped, Effect.provide(layer))) +const provideCurrentInstance = (effect: Effect.Effect, ctx: InstanceContext) => + effect.pipe(Effect.provideService(InstanceRef, ctx)) + +const load = (ctx: InstanceContext) => + Effect.runPromise( + Config.Service.use((svc) => provideCurrentInstance(svc.get(), ctx)).pipe(Effect.scoped, Effect.provide(layer)), + ) +const save = (config: Config.Info, ctx: InstanceContext) => + Effect.runPromise( + Config.Service.use((svc) => provideCurrentInstance(svc.update(config), ctx)).pipe( + Effect.scoped, + Effect.provide(layer), + ), + ) const saveGlobal = (config: Config.Info) => Effect.runPromise( Config.Service.use((svc) => svc.updateGlobal(config)).pipe( @@ -76,14 +87,26 @@ const clear = async (wait = false) => { await Effect.runPromise(Config.Service.use((svc) => svc.invalidate()).pipe(Effect.scoped, Effect.provide(layer))) if (wait) await InstanceRuntime.disposeAllInstances() } -const listDirs = () => - Effect.runPromise(Config.Service.use((svc) => svc.directories()).pipe(Effect.scoped, Effect.provide(layer))) +const listDirs = (ctx: InstanceContext) => + Effect.runPromise( + Config.Service.use((svc) => provideCurrentInstance(svc.directories(), ctx)).pipe( + Effect.scoped, + Effect.provide(layer), + ), + ) // kilocode_change start -const warnings = () => - Effect.runPromise(Config.Service.use((svc) => svc.warnings()).pipe(Effect.scoped, Effect.provide(layer))) +const warnings = (ctx: InstanceContext) => + Effect.runPromise( + Config.Service.use((svc) => provideCurrentInstance(svc.warnings(), ctx)).pipe(Effect.scoped, Effect.provide(layer)), + ) // kilocode_change end -const ready = () => - Effect.runPromise(Config.Service.use((svc) => svc.waitForDependencies()).pipe(Effect.scoped, Effect.provide(layer))) +const ready = (ctx: InstanceContext) => + Effect.runPromise( + Config.Service.use((svc) => provideCurrentInstance(svc.waitForDependencies(), ctx)).pipe( + Effect.scoped, + Effect.provide(layer), + ), + ) // Get managed config directory from environment (set in preload.ts) const managedConfigDir = process.env.KILO_TEST_MANAGED_CONFIG_DIR! @@ -115,16 +138,16 @@ async function check(map: (dir: string) => string) { await clear() try { await writeConfig(globalTmp.path, { - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", snapshot: false, }) - await WithInstance.provide({ + await withTestInstance({ directory: map(tmp.path), - fn: async () => { - const cfg = await load() + fn: async (ctx) => { + const cfg = await load(ctx) expect(cfg.snapshot).toBe(true) - expect(Instance.directory).toBe(Filesystem.resolve(tmp.path)) - expect(Instance.project.id).not.toBe(ProjectID.global) + expect(ctx.directory).toBe(Filesystem.resolve(tmp.path)) + expect(ctx.project.id).not.toBe(ProjectID.global) }, }) } finally { @@ -136,10 +159,10 @@ async function check(map: (dir: string) => string) { test("loads config with defaults when no files exist", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.username).toBeDefined() }, }) @@ -152,10 +175,10 @@ test("creates global jsonc config with schema when no global configs exist", asy await clear(true) try { - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - await load() + fn: async (ctx) => { + await load(ctx) }, }) @@ -177,10 +200,10 @@ test("does not create global config when KILO_CONFIG_DIR is set", async () => { await clear(true) try { - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - await load() + fn: async (ctx) => { + await load(ctx) }, }) @@ -204,10 +227,10 @@ test("loads JSON config file", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.model).toBe("test/model") expect(config.username).toBe("testuser") }, @@ -236,10 +259,10 @@ test("preserves Kilo provider free model metadata", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) const model = config.provider?.kilo?.models?.["free-e2e"] expect(model?.isFree).toBe(true) expect(model?.ai_sdk_provider).toBe("openai-compatible") @@ -257,10 +280,10 @@ test("loads shell config field", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.shell).toBe("bash") }, }) @@ -276,10 +299,10 @@ test("updates config and preserves empty shell sentinel", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - await save({ shell: "" }) + fn: async (ctx) => { + await save({ shell: "" }, ctx) const writtenConfig = await Filesystem.readJson<{ shell?: string }>(path.join(tmp.path, "kilo.json")) expect(writtenConfig.shell).toBe("") @@ -350,15 +373,15 @@ test("loads formatter boolean config", async () => { await using tmp = await tmpdir({ init: async (dir) => { await writeConfig(dir, { - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", formatter: true, }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.formatter).toBe(true) }, }) @@ -368,15 +391,15 @@ test("loads lsp boolean config", async () => { await using tmp = await tmpdir({ init: async (dir) => { await writeConfig(dir, { - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", lsp: true, }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.lsp).toBe(true) }, }) @@ -403,17 +426,17 @@ test("ignores legacy tui keys in opencode config", async () => { await using tmp = await tmpdir({ init: async (dir) => { await writeConfig(dir, { - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", model: "test/model", theme: "legacy", tui: { scroll_speed: 4 }, }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.model).toBe("test/model") expect((config as Record).theme).toBeUndefined() expect((config as Record).tui).toBeUndefined() @@ -435,10 +458,10 @@ test("loads JSONC config file", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.model).toBe("test/model") expect(config.username).toBe("testuser") }, @@ -463,10 +486,10 @@ test("jsonc overrides json in the same directory", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.model).toBe("base") expect(config.username).toBe("base") }, @@ -494,10 +517,10 @@ test("prefers .kilo directory config over legacy .kilocode", async () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.model).toBe("new/model") }, }) @@ -517,10 +540,10 @@ test("handles environment variable substitution", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.username).toBe("test-user") }, }) @@ -549,10 +572,10 @@ test("preserves env variables when adding $schema to config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.username).toBe("secret_value") // Read the file to verify the env variable was preserved @@ -646,10 +669,10 @@ test("handles file inclusion substitution", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.username).toBe("test-user") }, }) @@ -665,10 +688,10 @@ test("handles file inclusion with replacement tokens", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.username).toBe("const out = await Bun.$`echo hi`") }, }) @@ -686,10 +709,10 @@ test("validates config schema and reports warning on invalid fields", async () = }) await provideTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { // invalid schema surfaces as warnings, not a throw - await load() - const issues = await warnings() + await load(ctx) + const issues = await warnings(ctx) expect(issues.length).toBeGreaterThan(0) }, }) @@ -705,10 +728,10 @@ test("reports warning for invalid JSON", async () => { }) await provideTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { // invalid JSON surfaces as a warning, not a throw - await load() - const issues = await warnings() + await load(ctx) + const issues = await warnings(ctx) expect(issues.length).toBeGreaterThan(0) }, }) @@ -730,10 +753,10 @@ test("handles agent configuration", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test_agent"]).toEqual( expect.objectContaining({ model: "test/model", @@ -761,10 +784,10 @@ test("treats agent variant as model-scoped setting (not provider option)", async }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) const agent = config.agent?.["test_agent"] expect(agent?.variant).toBe("xhigh") @@ -791,10 +814,10 @@ test("handles command configuration", async () => { }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.command?.["test_command"]).toEqual({ template: "test template", description: "test command", @@ -816,10 +839,10 @@ test("migrates autoshare to share field", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.share).toBe("auto") expect(config.autoshare).toBe(true) }, @@ -843,10 +866,10 @@ test("migrates mode field to agent field", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test_mode"]).toEqual({ model: "test/model", temperature: 0.5, @@ -875,10 +898,10 @@ Test agent prompt`, ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test"]).toEqual( expect.objectContaining({ name: "test", @@ -908,10 +931,10 @@ Ordered permissions`, ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(Object.keys(config.agent?.ordered?.permission ?? {})).toEqual(["bash", "*", "edit"]) }, }) @@ -946,10 +969,10 @@ Nested agent prompt`, }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["helper"]).toMatchObject({ name: "helper", @@ -995,10 +1018,10 @@ Nested command template`, }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.command?.["hello"]).toEqual({ description: "Test command", @@ -1040,10 +1063,10 @@ Nested command template`, }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.command?.["hello"]).toEqual({ description: "Test command", @@ -1079,10 +1102,10 @@ Hello from new command`, }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.command?.["hello"]).toEqual({ description: "New command", @@ -1095,10 +1118,10 @@ Hello from new command`, test("gets config directories", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const dirs = await listDirs() + fn: async (ctx) => { + const dirs = await listDirs(ctx) expect(dirs.length).toBeGreaterThanOrEqual(1) }, }) @@ -1125,10 +1148,10 @@ test("does not try to install dependencies in read-only KILO_CONFIG_DIR", async process.env.KILO_CONFIG_DIR = tmp.extra try { - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - await load() + fn: async (ctx) => { + await load(ctx) }, }) } finally { @@ -1160,12 +1183,20 @@ test("installs dependencies in writable KILO_CONFIG_DIR", async () => { ) try { - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - await Effect.runPromise(Config.Service.use((svc) => svc.get()).pipe(Effect.scoped, Effect.provide(testLayer))) + fn: async (ctx) => { + await Effect.runPromise( + Config.Service.use((svc) => svc.get().pipe(Effect.provideService(InstanceRef, ctx))).pipe( + Effect.scoped, + Effect.provide(testLayer), + ), + ) await Effect.runPromise( - Config.Service.use((svc) => svc.waitForDependencies()).pipe(Effect.scoped, Effect.provide(testLayer)), + Config.Service.use((svc) => svc.waitForDependencies().pipe(Effect.provideService(InstanceRef, ctx))).pipe( + Effect.scoped, + Effect.provide(testLayer), + ), ) }, }) @@ -1221,8 +1252,8 @@ test("resolves scoped npm plugins in config", async () => { await provideTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) const pluginEntries = config.plugin ?? [] expect(pluginEntries).toContain("@scope/plugin") }, @@ -1259,8 +1290,8 @@ test("merges plugin arrays from global and local configs", async () => { await provideTestInstance({ directory: path.join(tmp.path, "project"), - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) const plugins = config.plugin ?? [] // Should contain both global and local plugins @@ -1293,10 +1324,10 @@ Helper subagent prompt`, ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["helper"]).toMatchObject({ name: "helper", model: "test/model", @@ -1332,10 +1363,10 @@ test("merges instructions arrays from global and local configs", async () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: path.join(tmp.path, "project"), - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) const instructions = config.instructions ?? [] expect(instructions).toContain("global-instructions.md") @@ -1371,10 +1402,10 @@ test("deduplicates duplicate instructions from global and local configs", async }, }) - await WithInstance.provide({ + await withTestInstance({ directory: path.join(tmp.path, "project"), - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) const instructions = config.instructions ?? [] expect(instructions).toContain("global-only.md") @@ -1418,8 +1449,8 @@ test("deduplicates duplicate plugins from global and local configs", async () => await provideTestInstance({ directory: path.join(tmp.path, "project"), - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) const plugins = config.plugin ?? [] // Should contain all unique plugins @@ -1450,7 +1481,7 @@ test("keeps plugin origins aligned with merged plugin list", async () => { await Filesystem.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", plugin: [["shared-plugin@1.0.0", { source: "global" }], "global-only@1.0.0"], }), ) @@ -1458,7 +1489,7 @@ test("keeps plugin origins aligned with merged plugin list", async () => { await Filesystem.write( path.join(local, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", plugin: [["shared-plugin@2.0.0", { source: "local" }], "local-only@1.0.0"], }), ) @@ -1467,8 +1498,8 @@ test("keeps plugin origins aligned with merged plugin list", async () => { await provideTestInstance({ directory: path.join(tmp.path, "project"), - fn: async () => { - const cfg = await load() + fn: async (ctx) => { + const cfg = await load(ctx) const plugins = cfg.plugin ?? [] const origins = cfg.plugin_origins ?? [] const names = plugins.map((item) => ConfigPlugin.pluginSpecifier(item)) @@ -1506,10 +1537,10 @@ test("migrates legacy tools config to permissions - allow", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test"]?.permission).toEqual({ bash: "allow", read: "allow", @@ -1537,10 +1568,10 @@ test("migrates legacy tools config to permissions - deny", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test"]?.permission).toEqual({ bash: "deny", webfetch: "deny", @@ -1567,10 +1598,10 @@ test("migrates legacy write tool to edit permission", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test"]?.permission).toEqual({ edit: "allow", }) @@ -1599,10 +1630,10 @@ test("managed settings override user settings", async () => { share: "disabled", }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.model).toBe("managed/model") expect(config.share).toBe("disabled") expect(config.username).toBe("testuser") @@ -1627,10 +1658,10 @@ test("managed settings override project settings", async () => { disabled_providers: ["openai"], }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.autoupdate).toBe(false) expect(config.disabled_providers).toEqual(["openai"]) }, @@ -1647,10 +1678,10 @@ test("missing managed settings file is not an error", async () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.model).toBe("user/model") }, }) @@ -1674,10 +1705,10 @@ test("migrates legacy edit tool to edit permission", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test"]?.permission).toEqual({ edit: "deny", }) @@ -1703,10 +1734,10 @@ test("migrates legacy patch tool to edit permission", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test"]?.permission).toEqual({ edit: "allow", }) @@ -1735,10 +1766,10 @@ test("migrates mixed legacy tools config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test"]?.permission).toEqual({ bash: "allow", edit: "allow", @@ -1770,10 +1801,10 @@ test("merges legacy tools with existing permission config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.agent?.["test"]?.permission).toEqual({ glob: "allow", bash: "allow", @@ -1792,6 +1823,7 @@ test("permission config preserves user key order", async () => { // sorts wildcards before specifics before iterating. See the // "fromConfig - specific key beats wildcard regardless of JSON key order" // test in test/permission/next.test.ts for the behavioural guarantee. + // kilocode_change start — isolate from global config to prevent cross-test contamination // (migrateBashPermission may write permission.bash to a global config file created by other // test files running in parallel, which mergeDeep then prepends to the project permission keys) await using globalTmp = await tmpdir() @@ -1799,12 +1831,13 @@ test("permission config preserves user key order", async () => { ;(Global.Path as { config: string }).config = globalTmp.path await clear(true) try { + // kilocode_change end await using tmp = await tmpdir({ init: async (dir) => { await Filesystem.write( - path.join(dir, "kilo.json"), + path.join(dir, "kilo.json"), // kilocode_change JSON.stringify({ - $schema: "https://app.kilo.ai/config.json", + $schema: "https://app.kilo.ai/config.json", // kilocode_change permission: { "*": "deny", edit: "ask", @@ -1821,10 +1854,10 @@ test("permission config preserves user key order", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(Object.keys(config.permission!)).toEqual([ "*", "edit", @@ -1839,10 +1872,12 @@ test("permission config preserves user key order", async () => { ]) }, }) + // kilocode_change start } finally { ;(Global.Path as { config: string }).config = prev await clear(true) } + // kilocode_change end }) test("config parser preserves permission order while rejecting unknown top-level keys", () => { @@ -1890,10 +1925,10 @@ test("local mcp accepts `env` as an alias for `environment`", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.mcp?.context7).toEqual({ type: "local", command: ["npx", "-y", "@upstash/context7-mcp"], @@ -1923,10 +1958,10 @@ test("local mcp prefers `environment` over `env` when both are present", async ( ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.mcp?.context7).toEqual({ type: "local", command: ["npx", "-y", "@upstash/context7-mcp"], @@ -1940,6 +1975,7 @@ test("local mcp prefers `environment` over `env` when both are present", async ( test("project config can override MCP server enabled status", async () => { await using tmp = await tmpdir({ init: async (dir) => { + // kilocode_change start — base config in .json, override in .jsonc (jsonc loads second and wins) // Simulates a base config with disabled MCP await Filesystem.write( path.join(dir, "kilo.json"), @@ -1973,12 +2009,13 @@ test("project config can override MCP server enabled status", async () => { }, }), ) + // kilocode_change end }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) // jira should be enabled (overridden by project config) expect(config.mcp?.jira).toEqual({ type: "remote", @@ -1998,6 +2035,7 @@ test("project config can override MCP server enabled status", async () => { test("MCP config deep merges preserving base config properties", async () => { await using tmp = await tmpdir({ init: async (dir) => { + // kilocode_change start — base config in .json, override in .jsonc (jsonc loads second and wins) // Base config with full MCP definition await Filesystem.write( path.join(dir, "kilo.json"), @@ -2016,6 +2054,7 @@ test("MCP config deep merges preserving base config properties", async () => { }), ) // Override just enables it, should preserve other properties + // kilocode_change end await Filesystem.write( path.join(dir, "kilo.jsonc"), JSON.stringify({ @@ -2031,10 +2070,10 @@ test("MCP config deep merges preserving base config properties", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.mcp?.myserver).toEqual({ type: "remote", url: "https://myserver.example.com/mcp", @@ -2082,10 +2121,10 @@ test("local .kilo config can override MCP from project config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.mcp?.docs?.enabled).toBe(true) }, }) @@ -2283,7 +2322,7 @@ test("wellknown remote_config supports templated env vars in headers", async () describe("resolvePluginSpec", () => { test("keeps package specs unchanged", async () => { await using tmp = await tmpdir() - const file = path.join(tmp.path, "kilo.json") + const file = path.join(tmp.path, "kilo.json") // kilocode_change expect(await ConfigPlugin.resolvePluginSpec("oh-my-opencode@2.4.3", file)).toBe("oh-my-opencode@2.4.3") expect(await ConfigPlugin.resolvePluginSpec("@scope/pkg", file)).toBe("@scope/pkg") }) @@ -2311,7 +2350,7 @@ describe("resolvePluginSpec", () => { }, }) - const file = path.join(tmp.path, "kilo.json") + const file = path.join(tmp.path, "kilo.json") // kilocode_change const hit = await ConfigPlugin.resolvePluginSpec("./plugin.ts", file) expect(ConfigPlugin.pluginSpecifier(hit)).toBe(pathToFileURL(path.join(tmp.path, "plugin.ts")).href) }) @@ -2330,7 +2369,7 @@ describe("resolvePluginSpec", () => { }, }) - const file = path.join(tmp.path, "kilo.json") + const file = path.join(tmp.path, "kilo.json") // kilocode_change const hit = await ConfigPlugin.resolvePluginSpec("./plugin", file) expect(ConfigPlugin.pluginSpecifier(hit)).toBe(pathToFileURL(path.join(tmp.path, "plugin")).href) }) @@ -2418,8 +2457,8 @@ describe("deduplicatePluginOrigins", () => { await provideTestInstance({ directory: path.join(tmp.path, "project"), - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) const plugins = config.plugin ?? [] expect(plugins.some((p) => ConfigPlugin.pluginSpecifier(p) === "my-plugin@1.0.0")).toBe(true) @@ -2448,10 +2487,10 @@ describe("KILO_DISABLE_PROJECT_CONFIG", () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) // Project config should NOT be loaded - model should be default, not "project/model" expect(config.model).not.toBe("project/model") expect(config.username).not.toBe("project-user") @@ -2467,6 +2506,7 @@ describe("KILO_DISABLE_PROJECT_CONFIG", () => { }) test("skips project .kilo/ directories when flag is set", async () => { + // kilocode_change - test .kilo/ directory (the test body writes to .kilo/command/) const originalEnv = process.env["KILO_DISABLE_PROJECT_CONFIG"] process.env["KILO_DISABLE_PROJECT_CONFIG"] = "true" @@ -2479,11 +2519,11 @@ describe("KILO_DISABLE_PROJECT_CONFIG", () => { await Filesystem.write(path.join(opencodeDir, "test-cmd.md"), "# Test Command\nThis is a test command.") }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const directories = await listDirs() - // Project .kilo should NOT be in directories list + fn: async (ctx) => { + const directories = await listDirs(ctx) + // Project .kilo should NOT be in directories list // kilocode_change const hasProjectOpencode = directories.some((d) => d.startsWith(tmp.path)) expect(hasProjectOpencode).toBe(false) }, @@ -2503,11 +2543,11 @@ describe("KILO_DISABLE_PROJECT_CONFIG", () => { try { await using tmp = await tmpdir() - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { // Should still get default config (from global or defaults) - const config = await load() + const config = await load(ctx) expect(config).toBeDefined() expect(config.username).toBeDefined() }, @@ -2545,12 +2585,12 @@ describe("KILO_DISABLE_PROJECT_CONFIG", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { // The relative instruction should be skipped without error // We're mainly verifying this doesn't throw and the config loads - const config = await load() + const config = await load(ctx) expect(config).toBeDefined() // The instruction should have been skipped (warning logged) // We can't easily test the warning was logged, but we verify @@ -2605,10 +2645,10 @@ describe("KILO_DISABLE_PROJECT_CONFIG", () => { process.env["KILO_DISABLE_PROJECT_CONFIG"] = "true" process.env["KILO_CONFIG_DIR"] = configDirTmp.path - await WithInstance.provide({ + await withTestInstance({ directory: projectTmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) // Should load from KILO_CONFIG_DIR, not project expect(config.model).toBe("configdir/model") }, @@ -2634,16 +2674,16 @@ describe("KILO_CONFIG_CONTENT token substitution", () => { const originalTestVar = process.env["TEST_CONFIG_VAR"] process.env["TEST_CONFIG_VAR"] = "test_api_key_12345" process.env["KILO_CONFIG_CONTENT"] = JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", username: "{env:TEST_CONFIG_VAR}", }) try { await using tmp = await tmpdir() - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.username).toBe("test_api_key_12345") }, }) @@ -2669,15 +2709,15 @@ describe("KILO_CONFIG_CONTENT token substitution", () => { init: async (dir) => { await Filesystem.write(path.join(dir, "api_key.txt"), "secret_key_from_file") process.env["KILO_CONFIG_CONTENT"] = JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", username: "{file:./api_key.txt}", }) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const config = await load() + fn: async (ctx) => { + const config = await load(ctx) expect(config.username).toBe("secret_key_from_file") }, }) @@ -2699,7 +2739,7 @@ test("parseManagedPlist strips MDM metadata keys", async () => { ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ - PayloadDisplayName: "OpenCode Managed", // kilocode_change + PayloadDisplayName: "OpenCode Managed", PayloadIdentifier: "ai.opencode.managed.test", PayloadType: "ai.opencode.managed", PayloadUUID: "AAAA-BBBB-CCCC", @@ -2727,7 +2767,7 @@ test("parseManagedPlist parses server settings", async () => { ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", server: { hostname: "127.0.0.1", mdns: false }, autoupdate: true, }), @@ -2747,7 +2787,7 @@ test("parseManagedPlist parses permission rules", async () => { ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", permission: { "*": "ask", bash: { "*": "ask", "rm -rf *": "deny", "curl *": "deny" }, @@ -2777,7 +2817,7 @@ test("parseManagedPlist parses enabled_providers", async () => { ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", enabled_providers: ["anthropic", "google"], }), ), @@ -2792,10 +2832,10 @@ test("parseManagedPlist handles empty config", async () => { const config = ConfigParse.schema( Config.Info, ConfigParse.jsonc( - await ConfigManaged.parseManagedPlist(JSON.stringify({ $schema: "https://opencode.ai/config.json" })), // kilocode_change + await ConfigManaged.parseManagedPlist(JSON.stringify({ $schema: "https://opencode.ai/config.json" })), "test:mobileconfig", ), "test:mobileconfig", ) - expect(config.$schema).toBe("https://opencode.ai/config.json") // kilocode_change + expect(config.$schema).toBe("https://opencode.ai/config.json") }) diff --git a/packages/opencode/test/control-plane/workspace.test.ts b/packages/opencode/test/control-plane/workspace.test.ts index 8b6f3ce8bc..bc8225581c 100644 --- a/packages/opencode/test/control-plane/workspace.test.ts +++ b/packages/opencode/test/control-plane/workspace.test.ts @@ -14,8 +14,7 @@ import { GlobalBus, type GlobalEvent } from "@/bus/global" import { Database } from "@/storage/db" import { ProjectID } from "@/project/schema" import { ProjectTable } from "@/project/project.sql" -import { Instance } from "@/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { context, type InstanceContext } from "@/project/instance-context" import { InstanceRef } from "@/effect/instance-ref" import { Session as SessionNs } from "@/session/session" import { SessionID } from "@/session/schema" @@ -122,12 +121,10 @@ afterEach(async () => { await resetDatabase() }) -async function withInstance(fn: (dir: string) => T | Promise) { +async function withInstance(fn: (ctx: InstanceContext) => T | Promise) { await using tmp = await tmpdir({ git: true }) - return await WithInstance.provide({ - directory: tmp.path, - fn: () => fn(tmp.path), - }) + const ctx = await AppRuntime.runPromise(InstanceStore.Service.use((store) => store.load({ directory: tmp.path }))) + return await context.provide(ctx, () => fn(ctx)) } async function initGitRepo(dir: string) { @@ -142,7 +139,18 @@ async function initGitRepo(dir: string) { await $`git commit -m "base"`.cwd(dir).quiet() } -const runWorkspace = (effect: Effect.Effect) => AppRuntime.runPromise(effect) +function currentInstance() { + try { + return context.use() + } catch { + return undefined + } +} + +const runWorkspace = (effect: Effect.Effect) => { + const ctx = currentInstance() + return AppRuntime.runPromise(ctx ? effect.pipe(Effect.provideService(InstanceRef, ctx)) : effect) +} const createWorkspace = (input: Workspace.CreateInput) => runWorkspace(Workspace.Service.use((workspace) => workspace.create(input))) const warpWorkspaceSession = (input: Workspace.SessionWarpInput) => @@ -416,16 +424,16 @@ describe("workspace CRUD", () => { }) test("list maps database rows, filters by project, and sorts by id", async () => { - await withInstance(async () => { + await withInstance(async (instance) => { const otherProjectID = ProjectID.make("project-other") insertProject(otherProjectID, "/tmp/other") - const a = workspaceInfo(Instance.project.id, "manual", { + const a = workspaceInfo(instance.project.id, "manual", { id: WorkspaceID.ascending("wrk_a_list"), branch: "a", directory: "/a", extra: { a: true }, }) - const b = workspaceInfo(Instance.project.id, "manual", { + const b = workspaceInfo(instance.project.id, "manual", { id: WorkspaceID.ascending("wrk_b_list"), branch: "b", directory: "/b", @@ -436,12 +444,12 @@ describe("workspace CRUD", () => { insertWorkspace(other) insertWorkspace(a) - expect(await listWorkspaces(Instance.project)).toEqual([a, b]) + expect(await listWorkspaces(instance.project)).toEqual([a, b]) }) }) test("create configures, persists, creates, starts local sync, and passes environment", async () => { - await withInstance(async (dir) => { + await withInstance(async (instance) => { process.env.KILO_AUTH_CONTENT = JSON.stringify({ test: { type: "api", key: "secret" } }) process.env.OTEL_EXPORTER_OTLP_HEADERS = "authorization=otel" process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://otel.test" @@ -449,7 +457,7 @@ describe("workspace CRUD", () => { const workspaceID = WorkspaceID.ascending("wrk_create_local") const type = unique("create-local") - const targetDir = path.join(dir, "created-local") + const targetDir = path.join(instance.directory, "created-local") const recorded = recordedAdapter({ configure(info) { return { @@ -467,13 +475,13 @@ describe("workspace CRUD", () => { return { type: "local", directory: targetDir } }, }) - registerAdapter(Instance.project.id, type, recorded.adapter) + registerAdapter(instance.project.id, type, recorded.adapter) const info = await createWorkspace({ id: workspaceID, type, branch: null, - projectID: Instance.project.id, + projectID: instance.project.id, extra: null, }) @@ -484,11 +492,11 @@ describe("workspace CRUD", () => { name: "Configured Name", directory: targetDir, extra: { configured: true }, - projectID: Instance.project.id, + projectID: instance.project.id, timeUsed: info.timeUsed, }) expect(await getWorkspace(workspaceID)).toEqual(info) - expect(await listWorkspaces(Instance.project)).toEqual([info]) + expect(await listWorkspaces(instance.project)).toEqual([info]) expect(recorded.calls.configure).toHaveLength(1) expect(recorded.calls.configure[0]).toMatchObject({ id: workspaceID, type, directory: null }) expect(recorded.calls.create).toHaveLength(1) @@ -499,7 +507,7 @@ describe("workspace CRUD", () => { name: "Configured Name", directory: targetDir, extra: { configured: true }, - projectID: Instance.project.id, + projectID: instance.project.id, }) expect(JSON.parse(recorded.calls.create[0].env.KILO_AUTH_CONTENT ?? "{}")).toEqual({ test: { type: "api", key: "secret" }, @@ -517,10 +525,10 @@ describe("workspace CRUD", () => { }) test("create propagates configure failures and does not insert a workspace", async () => { - await withInstance(async () => { + await withInstance(async (instance) => { const type = unique("configure-failure") registerAdapter( - Instance.project.id, + instance.project.id, type, recordedAdapter({ configure() { @@ -533,14 +541,14 @@ describe("workspace CRUD", () => { ) await expect( - createWorkspace({ type, branch: null, projectID: Instance.project.id, extra: null }), + createWorkspace({ type, branch: null, projectID: instance.project.id, extra: null }), ).rejects.toThrow("configure exploded") - expect(await listWorkspaces(Instance.project)).toEqual([]) + expect(await listWorkspaces(instance.project)).toEqual([]) }) }) test("create leaves the inserted row when adapter create fails", async () => { - await withInstance(async () => { + await withInstance(async (instance) => { const type = unique("create-failure") const recorded = recordedAdapter({ async create() { @@ -550,13 +558,13 @@ describe("workspace CRUD", () => { return { type: "local", directory: "/unused" } }, }) - registerAdapter(Instance.project.id, type, recorded.adapter) + registerAdapter(instance.project.id, type, recorded.adapter) await expect( - createWorkspace({ type, branch: "branch", projectID: Instance.project.id, extra: { x: 1 } }), + createWorkspace({ type, branch: "branch", projectID: instance.project.id, extra: { x: 1 } }), ).rejects.toThrow("create exploded") - const rows = await listWorkspaces(Instance.project) + const rows = await listWorkspaces(instance.project) expect(rows).toHaveLength(1) expect(rows[0]).toMatchObject({ type, branch: "branch", extra: { x: 1 } }) expect(recorded.calls.target).toHaveLength(0) @@ -565,13 +573,13 @@ describe("workspace CRUD", () => { }) test("create returns after a local workspace reports error", async () => { - await withInstance(async (dir) => { + await withInstance(async (instance) => { const type = unique("local-error") - const missing = path.join(dir, "missing-local-target") + const missing = path.join(instance.directory, "missing-local-target") const recorded = localAdapter(missing, { createDir: false }) - registerAdapter(Instance.project.id, type, recorded.adapter) + registerAdapter(instance.project.id, type, recorded.adapter) - const info = await createWorkspace({ type, branch: null, projectID: Instance.project.id, extra: null }) + const info = await createWorkspace({ type, branch: null, projectID: instance.project.id, extra: null }) expect(info.directory).toBe(missing) expect((await workspaceStatus()).find((item) => item.workspaceID === info.id)?.status).toBe("error") @@ -580,12 +588,12 @@ describe("workspace CRUD", () => { }) test("syncList registers adapter-listed workspaces that are missing by name", async () => { - await withInstance(async (dir) => { + await withInstance(async (instance) => { const type = unique("list-sync") - const existing = workspaceInfo(Instance.project.id, type, { + const existing = workspaceInfo(instance.project.id, type, { id: WorkspaceID.ascending("wrk_list_sync_existing"), name: "existing", - directory: path.join(dir, "existing"), + directory: path.join(instance.directory, "existing"), }) insertWorkspace(existing) @@ -593,9 +601,9 @@ describe("workspace CRUD", () => { type, name: "discovered", branch: "feature/discovered", - directory: path.join(dir, "discovered"), + directory: path.join(instance.directory, "discovered"), extra: { source: "adapter" }, - projectID: Instance.project.id, + projectID: instance.project.id, } const recorded = recordedAdapter({ list() { @@ -604,26 +612,26 @@ describe("workspace CRUD", () => { type, name: existing.name, branch: "ignored", - directory: path.join(dir, "ignored"), + directory: path.join(instance.directory, "ignored"), extra: null, - projectID: Instance.project.id, + projectID: instance.project.id, }, discovered, ] }, target(info) { - return { type: "local", directory: info.directory ?? dir } + return { type: "local", directory: info.directory ?? instance.directory } }, }) - registerAdapter(Instance.project.id, type, recorded.adapter) + registerAdapter(instance.project.id, type, recorded.adapter) - await syncListWorkspaces(Instance.project) - const synced = (await listWorkspaces(Instance.project)).filter((item) => item.name === discovered.name) + await syncListWorkspaces(instance.project) + const synced = (await listWorkspaces(instance.project)).filter((item) => item.name === discovered.name) expect(synced).toHaveLength(1) expect(synced[0]).toMatchObject(discovered) expect(synced[0]?.id).toStartWith("wrk_") - expect(await listWorkspaces(Instance.project)).toEqual(expect.arrayContaining([existing, synced[0]])) + expect(await listWorkspaces(instance.project)).toEqual(expect.arrayContaining([existing, synced[0]])) expect(recorded.calls.list).toBe(1) expect(recorded.calls.configure).toHaveLength(0) expect(recorded.calls.create).toHaveLength(0) @@ -632,7 +640,7 @@ describe("workspace CRUD", () => { }) test("syncList calls every registered adapter with a list method", async () => { - await withInstance(async (dir) => { + await withInstance(async (instance) => { const typeA = unique("list-sync-a") const typeB = unique("list-sync-b") const adapterA = recordedAdapter({ @@ -642,14 +650,14 @@ describe("workspace CRUD", () => { type: typeA, name: "adapter-a", branch: null, - directory: path.join(dir, "adapter-a"), + directory: path.join(instance.directory, "adapter-a"), extra: null, - projectID: Instance.project.id, + projectID: instance.project.id, }, ] }, target(info) { - return { type: "local", directory: info.directory ?? dir } + return { type: "local", directory: info.directory ?? instance.directory } }, }) const adapterB = recordedAdapter({ @@ -659,27 +667,27 @@ describe("workspace CRUD", () => { type: typeB, name: "adapter-b", branch: null, - directory: path.join(dir, "adapter-b"), + directory: path.join(instance.directory, "adapter-b"), extra: null, - projectID: Instance.project.id, + projectID: instance.project.id, }, ] }, target(info) { - return { type: "local", directory: info.directory ?? dir } + return { type: "local", directory: info.directory ?? instance.directory } }, }) const noList = recordedAdapter({ target() { - return { type: "local", directory: dir } + return { type: "local", directory: instance.directory } }, }) - registerAdapter(Instance.project.id, typeA, adapterA.adapter) - registerAdapter(Instance.project.id, typeB, adapterB.adapter) - registerAdapter(Instance.project.id, unique("list-sync-none"), noList.adapter) + registerAdapter(instance.project.id, typeA, adapterA.adapter) + registerAdapter(instance.project.id, typeB, adapterB.adapter) + registerAdapter(instance.project.id, unique("list-sync-none"), noList.adapter) - await syncListWorkspaces(Instance.project) - const synced = await listWorkspaces(Instance.project) + await syncListWorkspaces(instance.project) + const synced = await listWorkspaces(instance.project) expect( synced @@ -719,11 +727,13 @@ describe("workspace CRUD", () => { (dir) => Effect.gen(function* () { const workspace = yield* Workspace.Service + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) const type = unique("remote-create") const recorded = remoteAdapter(`${url}/base/?ignored=1#hash`, { directory: dir }) - registerAdapter(Instance.project.id, type, recorded.adapter) + registerAdapter(instance.project.id, type, recorded.adapter) - const info = yield* workspace.create({ type, branch: null, projectID: Instance.project.id, extra: null }) + const info = yield* workspace.create({ type, branch: null, projectID: instance.project.id, extra: null }) expect( calls.map((call) => `${call.method} ${call.url.pathname}${call.url.search}${call.url.hash}`), @@ -782,11 +792,11 @@ describe("workspace CRUD", () => { ) test("remove still deletes the row when the adapter cannot remove resources", async () => { - await withInstance(async () => { + await withInstance(async (instance) => { const type = unique("remove-throws") - const info = workspaceInfo(Instance.project.id, type, { id: WorkspaceID.ascending("wrk_remove_throws") }) + const info = workspaceInfo(instance.project.id, type, { id: WorkspaceID.ascending("wrk_remove_throws") }) registerAdapter( - Instance.project.id, + instance.project.id, type, recordedAdapter({ async remove() { @@ -911,24 +921,26 @@ describe("workspace CRUD", () => { ) test("sessionWarp detaches to the source project when invoked from a workspace instance", async () => { - await withInstance(async () => { - const projectID = Instance.project.id + await withInstance(async (instance) => { + const projectID = instance.project.id await using workspaceTmp = await tmpdir({ git: true }) const previousType = unique("warp-detach-workspace-instance") const previous = workspaceInfo(projectID, previousType) insertWorkspace(previous) registerAdapter(projectID, previousType, localAdapter(workspaceTmp.path, { createDir: false }).adapter) - const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({}))) + const session = await AppRuntime.runPromise( + SessionNs.Service.use((svc) => svc.create({})).pipe(Effect.provideService(InstanceRef, instance)), + ) attachSessionToWorkspace(session.id, previous.id) - const workspaceProjectID = await WithInstance.provide({ - directory: workspaceTmp.path, - fn: async () => { - const id = Instance.project.id - expect(id).not.toBe(projectID) - await warpWorkspaceSession({ workspaceID: null, sessionID: session.id }) - return id - }, + const workspaceCtx = await AppRuntime.runPromise( + InstanceStore.Service.use((store) => store.load({ directory: workspaceTmp.path })), + ) + const workspaceProjectID = await context.provide(workspaceCtx, async () => { + const id = workspaceCtx.project.id + expect(id).not.toBe(projectID) + await warpWorkspaceSession({ workspaceID: null, sessionID: session.id }) + return id }) expect( @@ -988,14 +1000,16 @@ describe("workspace CRUD", () => { Effect.gen(function* () { const workspace = yield* Workspace.Service const sessionSvc = yield* SessionNs.Service + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) const previousType = unique("warp-remote-source") const targetType = unique("warp-remote-target") - const previous = workspaceInfo(Instance.project.id, previousType) - const target = workspaceInfo(Instance.project.id, targetType, { directory: "remote-target-dir" }) + const previous = workspaceInfo(instance.project.id, previousType) + const target = workspaceInfo(instance.project.id, targetType, { directory: "remote-target-dir" }) insertWorkspace(previous) insertWorkspace(target) - registerAdapter(Instance.project.id, previousType, remoteAdapter(`${url}/warp-source`).adapter) - registerAdapter(Instance.project.id, targetType, remoteAdapter(`${url}/warp-target`).adapter) + registerAdapter(instance.project.id, previousType, remoteAdapter(`${url}/warp-source`).adapter) + registerAdapter(instance.project.id, targetType, remoteAdapter(`${url}/warp-target`).adapter) const session = yield* sessionSvc.create({}) attachSessionToWorkspace(session.id, previous.id) historySessionID = session.id @@ -1197,15 +1211,17 @@ describe("workspace sync state", () => { Effect.gen(function* () { const workspace = yield* Workspace.Service const sessionSvc = yield* SessionNs.Service + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) const captured = captureGlobalEvents() try { const type = unique("remote-start") - const info = workspaceInfo(Instance.project.id, type) + const info = workspaceInfo(instance.project.id, type) insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/sync`).adapter) + registerAdapter(instance.project.id, type, remoteAdapter(`${url}/sync`).adapter) attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id) - yield* workspace.startWorkspaceSyncing(Instance.project.id) + yield* workspace.startWorkspaceSyncing(instance.project.id) yield* eventuallyEffect( Effect.gen(function* () { expect((yield* workspace.status()).find((item) => item.workspaceID === info.id)?.status).toBe( @@ -1213,7 +1229,7 @@ describe("workspace sync state", () => { ) }), ) - yield* workspace.startWorkspaceSyncing(Instance.project.id) + yield* workspace.startWorkspaceSyncing(instance.project.id) yield* Effect.sleep("25 millis") expect( @@ -1252,13 +1268,15 @@ describe("workspace sync state", () => { Effect.gen(function* () { const workspace = yield* Workspace.Service const sessionSvc = yield* SessionNs.Service + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) const type = unique("remote-connect-fail") - const info = workspaceInfo(Instance.project.id, type) + const info = workspaceInfo(instance.project.id, type) insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/failed`).adapter) + registerAdapter(instance.project.id, type, remoteAdapter(`${url}/failed`).adapter) attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id) - yield* workspace.startWorkspaceSyncing(Instance.project.id) + yield* workspace.startWorkspaceSyncing(instance.project.id) yield* eventuallyEffect( Effect.gen(function* () { @@ -1292,13 +1310,15 @@ describe("workspace sync state", () => { Effect.gen(function* () { const workspace = yield* Workspace.Service const sessionSvc = yield* SessionNs.Service + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) const type = unique("remote-history-fail") - const info = workspaceInfo(Instance.project.id, type) + const info = workspaceInfo(instance.project.id, type) insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/history-failed`).adapter) + registerAdapter(instance.project.id, type, remoteAdapter(`${url}/history-failed`).adapter) attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id) - yield* workspace.startWorkspaceSyncing(Instance.project.id) + yield* workspace.startWorkspaceSyncing(instance.project.id) yield* eventuallyEffect( Effect.gen(function* () { @@ -1347,18 +1367,20 @@ describe("workspace sync state", () => { Effect.gen(function* () { const workspace = yield* Workspace.Service const sessionSvc = yield* SessionNs.Service + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) const captured = captureGlobalEvents() try { const type = unique("history-replay") - const info = workspaceInfo(Instance.project.id, type) + const info = workspaceInfo(instance.project.id, type) insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/history`).adapter) + registerAdapter(instance.project.id, type, remoteAdapter(`${url}/history`).adapter) const session = yield* sessionSvc.create({ title: "before history" }) attachSessionToWorkspace(session.id, info.id) historySessionID = session.id historyNextSeq = (sessionSequence(session.id) ?? -1) + 1 - yield* workspace.startWorkspaceSyncing(Instance.project.id) + yield* workspace.startWorkspaceSyncing(instance.project.id) yield* eventuallyEffect( Effect.gen(function* () { @@ -1414,15 +1436,17 @@ describe("workspace sync state", () => { Effect.gen(function* () { const workspace = yield* Workspace.Service const sessionSvc = yield* SessionNs.Service + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) const captured = captureGlobalEvents() try { const type = unique("sse-forward") - const info = workspaceInfo(Instance.project.id, type) + const info = workspaceInfo(instance.project.id, type) insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/sse-forward`).adapter) + registerAdapter(instance.project.id, type, remoteAdapter(`${url}/sse-forward`).adapter) attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id) - yield* workspace.startWorkspaceSyncing(Instance.project.id) + yield* workspace.startWorkspaceSyncing(instance.project.id) yield* eventuallyEffect( Effect.sync(() => @@ -1495,18 +1519,20 @@ describe("workspace sync state", () => { Effect.gen(function* () { const workspace = yield* Workspace.Service const sessionSvc = yield* SessionNs.Service + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) const captured = captureGlobalEvents() try { const type = unique("sse-sync") - const info = workspaceInfo(Instance.project.id, type) + const info = workspaceInfo(instance.project.id, type) insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/sse-sync`).adapter) + registerAdapter(instance.project.id, type, remoteAdapter(`${url}/sse-sync`).adapter) const session = yield* sessionSvc.create({ title: "before sse" }) attachSessionToWorkspace(session.id, info.id) sseSessionID = session.id sseNextSeq = (sessionSequence(session.id) ?? -1) + 1 - yield* workspace.startWorkspaceSyncing(Instance.project.id) + yield* workspace.startWorkspaceSyncing(instance.project.id) yield* eventuallyEffect( Effect.gen(function* () { diff --git a/packages/opencode/test/effect/instance-state.test.ts b/packages/opencode/test/effect/instance-state.test.ts index b928f16bc6..d983de89a8 100644 --- a/packages/opencode/test/effect/instance-state.test.ts +++ b/packages/opencode/test/effect/instance-state.test.ts @@ -1,16 +1,21 @@ -import { afterEach, expect } from "bun:test" +import { expect } from "bun:test" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { $ } from "bun" import { Context, Deferred, Duration, Effect, Exit, Fiber, Layer } from "effect" import { InstanceState } from "@/effect/instance-state" -import { Instance } from "../../src/project/instance" -import { disposeAllInstances, provideInstance, reloadTestInstance, tmpdirScoped } from "../fixture/fixture" +import { + disposeAllInstancesEffect, + provideInstanceEffect, + reloadInstance, + testInstanceStoreLayer, + tmpdirScoped, +} from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(CrossSpawnSpawner.defaultLayer) +const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, testInstanceStoreLayer)) const access = (state: InstanceState.InstanceState, dir: string) => - InstanceState.get(state).pipe(provideInstance(dir)) + InstanceState.get(state).pipe(provideInstanceEffect(dir)) const tmpdirGitScoped = Effect.gen(function* () { const dir = yield* tmpdirScoped({ git: true }) @@ -18,10 +23,6 @@ const tmpdirGitScoped = Effect.gen(function* () { return dir }) -afterEach(async () => { - await disposeAllInstances() -}) - it.live("InstanceState caches values per directory", () => Effect.gen(function* () { const dir = yield* tmpdirScoped() @@ -69,7 +70,7 @@ it.live("InstanceState invalidates on reload", () => ) const a = yield* access(state, dir) - yield* Effect.promise(() => reloadTestInstance({ directory: dir })) + yield* reloadInstance({ directory: dir }) const b = yield* access(state, dir) expect(a).not.toBe(b) @@ -94,7 +95,7 @@ it.live("InstanceState invalidates on disposeAll", () => yield* access(state, one) yield* access(state, two) - yield* Effect.promise(disposeAllInstances) + yield* disposeAllInstancesEffect expect(seen.sort()).toEqual([one, two].sort()) }), @@ -126,8 +127,8 @@ it.live("InstanceState.get reads the current directory lazily", () => } yield* Effect.gen(function* () { - const a = yield* Test.use((svc) => svc.get()).pipe(provideInstance(one)) - const b = yield* Test.use((svc) => svc.get()).pipe(provideInstance(two)) + const a = yield* Test.use((svc) => svc.get()).pipe(provideInstanceEffect(one)) + const b = yield* Test.use((svc) => svc.get()).pipe(provideInstanceEffect(two)) expect(a).toBe(one) expect(b).toBe(two) @@ -178,7 +179,7 @@ it.live("InstanceState preserves directory across async boundaries", () => yield* Effect.gen(function* () { const [a, b, c] = yield* Effect.all( - [one, two, three].map((dir) => Test.use((svc) => svc.get()).pipe(provideInstance(dir))), + [one, two, three].map((dir) => Test.use((svc) => svc.get()).pipe(provideInstanceEffect(dir))), { concurrency: "unbounded" }, ) @@ -225,7 +226,7 @@ it.live("InstanceState survives high-contention concurrent access", () => yield* Effect.gen(function* () { const results = yield* Effect.all( - dirs.map((dir) => Test.use((svc) => svc.get()).pipe(provideInstance(dir))), + dirs.map((dir) => Test.use((svc) => svc.get()).pipe(provideInstanceEffect(dir))), { concurrency: "unbounded" }, ) @@ -264,19 +265,16 @@ it.live("InstanceState correct after interleaved init and dispose", () => } yield* Effect.gen(function* () { - const a = yield* Test.use((svc) => svc.get()).pipe(provideInstance(one)) + const a = yield* Test.use((svc) => svc.get()).pipe(provideInstanceEffect(one)) expect(a).toBe(one) const [, b] = yield* Effect.all( - [ - Effect.promise(() => reloadTestInstance({ directory: one })), - Test.use((svc) => svc.get()).pipe(provideInstance(two)), - ], + [reloadInstance({ directory: one }), Test.use((svc) => svc.get()).pipe(provideInstanceEffect(two))], { concurrency: "unbounded" }, ) expect(b).toBe(two) - const c = yield* Test.use((svc) => svc.get()).pipe(provideInstance(one)) + const c = yield* Test.use((svc) => svc.get()).pipe(provideInstanceEffect(one)) expect(c).toBe(one) }).pipe(Effect.provide(Test.layer)) }), @@ -344,9 +342,9 @@ it.live("InstanceState survives deferred resume from the same instance context", yield* Effect.gen(function* () { const gate = yield* Deferred.make() - const fiber = yield* Test.use((svc) => svc.get(gate)).pipe(provideInstance(dir), Effect.forkScoped) + const fiber = yield* Test.use((svc) => svc.get(gate)).pipe(provideInstanceEffect(dir), Effect.forkScoped) - yield* Deferred.succeed(gate, undefined).pipe(provideInstance(dir)) + yield* Deferred.succeed(gate, undefined).pipe(provideInstanceEffect(dir)) const exit = yield* Fiber.await(fiber) expect(Exit.isSuccess(exit)).toBe(true) @@ -381,7 +379,7 @@ it.live("InstanceState survives deferred resume outside ALS when InstanceRef is yield* Effect.gen(function* () { const gate = yield* Deferred.make() - const fiber = yield* Test.use((svc) => svc.get(gate)).pipe(provideInstance(dir), Effect.forkScoped) + const fiber = yield* Test.use((svc) => svc.get(gate)).pipe(provideInstanceEffect(dir), Effect.forkScoped) yield* Deferred.succeed(gate, undefined) const exit = yield* Fiber.await(fiber) diff --git a/packages/opencode/test/effect/runtime-flags.test.ts b/packages/opencode/test/effect/runtime-flags.test.ts index 1744ffb341..712e78752d 100644 --- a/packages/opencode/test/effect/runtime-flags.test.ts +++ b/packages/opencode/test/effect/runtime-flags.test.ts @@ -27,6 +27,9 @@ describe("RuntimeFlags", () => { KILO_DISABLE_CHANNEL_DB: "true", KILO_AUTO_SHARE: "true", KILO_DISABLE_EMBEDDED_WEB_UI: "true", + KILO_DISABLE_EXTERNAL_SKILLS: "true", + KILO_DISABLE_LSP_DOWNLOAD: "true", + KILO_SKIP_MIGRATIONS: "true", KILO_EXPERIMENTAL: "true", KILO_ENABLE_EXA: "true", KILO_ENABLE_PARALLEL: "true", @@ -42,6 +45,10 @@ describe("RuntimeFlags", () => { expect(flags.disableDefaultPlugins).toBe(true) expect(flags.disableChannelDb).toBe(true) expect(flags.disableEmbeddedWebUi).toBe(true) + expect(flags.disableExternalSkills).toBe(true) + expect(flags.disableLspDownload).toBe(true) + expect(flags.skipMigrations).toBe(true) + expect(flags.disableClaudeCodePrompt).toBe(false) expect(flags.enableExa).toBe(true) expect(flags.enableParallel).toBe(true) expect(flags.enableExperimentalModels).toBe(true) @@ -84,10 +91,15 @@ describe("RuntimeFlags", () => { expect(flags.disableDefaultPlugins).toBe(true) expect(flags.disableChannelDb).toBe(false) expect(flags.disableEmbeddedWebUi).toBe(false) + expect(flags.disableExternalSkills).toBe(false) + expect(flags.disableLspDownload).toBe(false) + expect(flags.skipMigrations).toBe(false) + expect(flags.disableClaudeCodePrompt).toBe(false) expect(flags.disableClaudeCodeSkills).toBe(false) expect(flags.enableExa).toBe(false) expect(flags.experimentalIconDiscovery).toBe(false) expect(flags.experimentalOxfmt).toBe(false) + expect(flags.outputTokenMax).toBeUndefined() expect(flags.bashDefaultTimeoutMs).toBe(1_000) expect(flags.enableExperimentalModels).toBe(false) expect(flags.client).toBe("cli") @@ -102,6 +114,78 @@ describe("RuntimeFlags", () => { }), ) + it.effect("disableExternalSkills defaults to false", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({}))) + + expect(flags.disableExternalSkills).toBe(false) + }), + ) + + it.effect("disableExternalSkills reads KILO_DISABLE_EXTERNAL_SKILLS", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ KILO_DISABLE_EXTERNAL_SKILLS: "true" }))) + + expect(flags.disableExternalSkills).toBe(true) + }), + ) + + it.effect("disableLspDownload defaults to false", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({}))) + + expect(flags.disableLspDownload).toBe(false) + }), + ) + + it.effect("disableLspDownload reads KILO_DISABLE_LSP_DOWNLOAD", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ KILO_DISABLE_LSP_DOWNLOAD: "true" }))) + + expect(flags.disableLspDownload).toBe(true) + }), + ) + + it.effect("skipMigrations defaults to false", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({}))) + + expect(flags.skipMigrations).toBe(false) + }), + ) + + it.effect("skipMigrations reads KILO_SKIP_MIGRATIONS", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ KILO_SKIP_MIGRATIONS: "true" }))) + + expect(flags.skipMigrations).toBe(true) + }), + ) + + it.effect("disableClaudeCodePrompt defaults to false", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({}))) + + expect(flags.disableClaudeCodePrompt).toBe(false) + }), + ) + + it.effect("disableClaudeCodePrompt reads KILO_DISABLE_CLAUDE_CODE_PROMPT", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ KILO_DISABLE_CLAUDE_CODE_PROMPT: "true" }))) + + expect(flags.disableClaudeCodePrompt).toBe(true) + }), + ) + + it.effect("disableClaudeCodePrompt inherits KILO_DISABLE_CLAUDE_CODE", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ KILO_DISABLE_CLAUDE_CODE: "true" }))) + + expect(flags.disableClaudeCodePrompt).toBe(true) + }), + ) + it.effect("experimentalIconDiscovery reads KILO_EXPERIMENTAL_ICON_DISCOVERY", () => Effect.gen(function* () { const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ KILO_EXPERIMENTAL_ICON_DISCOVERY: "true" }))) @@ -183,6 +267,35 @@ describe("RuntimeFlags", () => { ) } + for (const input of [ + { name: "absent", config: {}, expected: undefined }, + { + name: "valid positive integer", + config: { KILO_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "1234" }, + expected: 1234, + }, + { + name: "invalid string", + config: { KILO_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "nope" }, + expected: undefined, + }, + { name: "zero", config: { KILO_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "0" }, expected: undefined }, + { name: "negative", config: { KILO_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "-1" }, expected: undefined }, + { + name: "non-integer", + config: { KILO_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "1.5" }, + expected: undefined, + }, + ]) { + it.effect(`parses outputTokenMax from config: ${input.name}`, () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(fromConfig(input.config))) + + expect(flags.outputTokenMax).toBe(input.expected) + }), + ) + } + it.effect("layer ignores the active ConfigProvider for omitted test overrides", () => Effect.gen(function* () { const flags = yield* readFlags.pipe( @@ -192,6 +305,9 @@ describe("RuntimeFlags", () => { ConfigProvider.fromUnknown({ KILO_PURE: "true", KILO_DISABLE_DEFAULT_PLUGINS: "true", + KILO_DISABLE_EXTERNAL_SKILLS: "true", + KILO_DISABLE_LSP_DOWNLOAD: "true", + KILO_SKIP_MIGRATIONS: "true", KILO_EXPERIMENTAL: "true", KILO_ENABLE_EXA: "true", KILO_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1234", @@ -205,10 +321,15 @@ describe("RuntimeFlags", () => { expect(flags.disableDefaultPlugins).toBe(false) expect(flags.disableChannelDb).toBe(false) expect(flags.disableEmbeddedWebUi).toBe(false) + expect(flags.disableExternalSkills).toBe(false) + expect(flags.disableLspDownload).toBe(false) + expect(flags.skipMigrations).toBe(false) + expect(flags.disableClaudeCodePrompt).toBe(false) expect(flags.disableClaudeCodeSkills).toBe(false) expect(flags.enableExa).toBe(false) expect(flags.experimentalIconDiscovery).toBe(false) expect(flags.experimentalOxfmt).toBe(false) + expect(flags.outputTokenMax).toBeUndefined() expect(flags.bashDefaultTimeoutMs).toBeUndefined() expect(flags.client).toBe("cli") }), diff --git a/packages/opencode/test/file/watcher.test.ts b/packages/opencode/test/file/watcher.test.ts index f5495218b4..8a812fcd0d 100644 --- a/packages/opencode/test/file/watcher.test.ts +++ b/packages/opencode/test/file/watcher.test.ts @@ -1,5 +1,6 @@ import { describe, expect } from "bun:test" import path from "path" +import { realpath } from "fs/promises" // kilocode_change - accept canonical paths from symlinked git directories import { AppFileSystem } from "@opencode-ai/core/filesystem" import { ConfigProvider, Deferred, Effect, Layer, Option } from "effect" import { TestInstance, provideInstance } from "../fixture/fixture" @@ -133,11 +134,12 @@ function ready(directory: string) { if (!(yield* fs.existsSafe(head))) return + const realHead = yield* Effect.promise(() => realpath(head).catch(() => head)) // kilocode_change const branch = `watch-${Math.random().toString(36).slice(2)}` const hash = (yield* git.run(["rev-parse", "HEAD"], { cwd: directory })).text() yield* nextUpdate( directory, - (evt) => evt.file === head && evt.event !== "unlink", + (evt) => (evt.file === head || evt.file === realHead) && evt.event !== "unlink", // kilocode_change fs .writeFileString(path.join(directory, ".git", "refs", "heads", branch), hash.trim() + "\n") .pipe(Effect.andThen(fs.writeFileString(head, `ref: refs/heads/${branch}\n`))), @@ -260,4 +262,58 @@ describeWatcher("FileWatcher", () => { }), { git: true }, ) + + // Symlink support varies by platform; skip where unavailable + const describeSymlink = process.platform !== "win32" ? describe : describe.skip + + describeSymlink("symlinked .git", () => { + it.instance( + "publishes .git/HEAD events through a symlinked .git directory", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const fs = yield* AppFileSystem.Service + const git = yield* Git.Service + const dir = test.directory + const actualGit = path.join(dir, "..", "tmp_actual_git_" + Math.random().toString(36).slice(2)) + + // Move .git to a sibling directory and replace with a symlink + yield* Effect.promise(() => import("fs")).pipe( + Effect.flatMap((nodeFs) => + Effect.all([ + Effect.promise(() => nodeFs.promises.rename(path.join(dir, ".git"), actualGit)), + Effect.promise(() => nodeFs.promises.symlink(actualGit, path.join(dir, ".git"))), + ]), + ), + ) + + yield* Effect.acquireRelease(Effect.succeed(actualGit), (p) => + Effect.promise(() => + import("fs").then((f) => f.promises.rm(p, { recursive: true, force: true }).catch(() => undefined)), + ), + ) + + const head = path.join(dir, ".git", "HEAD") + const branch = `watch-${Math.random().toString(36).slice(2)}` + yield* git.run(["branch", branch], { cwd: dir }) + + yield* withWatcher( + dir, + nextUpdate( + dir, + (evt) => evt.file === path.join(actualGit, "HEAD") && evt.event !== "unlink", + fs.writeFileString(head, `ref: refs/heads/${branch}\n`), + ).pipe( + Effect.tap((evt) => + Effect.sync(() => { + expect(evt.file).toBe(path.join(actualGit, "HEAD")) + expect(["add", "change"]).toContain(evt.event) + }), + ), + ), + ) + }), + { git: true }, + ) + }) }) diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts index 1075061df4..3b6bd6717b 100644 --- a/packages/opencode/test/fixture/fixture.ts +++ b/packages/opencode/test/fixture/fixture.ts @@ -11,30 +11,42 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import type { Config } from "@/config/config" import { InstanceRef } from "../../src/effect/instance-ref" import { InstanceBootstrap } from "../../src/project/bootstrap-service" +import { context as instanceContext, type InstanceContext } from "../../src/project/instance-context" // kilocode_change import { InstanceRuntime } from "../../src/project/instance-runtime" import { InstanceStore } from "../../src/project/instance-store" -import { Instance } from "../../src/project/instance" import { TestLLMServer } from "../lib/llm-server" import { remove as cleanup } from "../kilocode/cleanup" // kilocode_change const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) -const testInstanceRuntime = ManagedRuntime.make( - InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap), Layer.provideMerge(Observability.layer)), -) +export const testInstanceStoreLayer = InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap)) +const testInstanceRuntime = ManagedRuntime.make(testInstanceStoreLayer.pipe(Layer.provideMerge(Observability.layer))) const runTestInstanceStore = (fn: (store: InstanceStore.Interface) => Effect.Effect) => testInstanceRuntime.runPromise(InstanceStore.Service.use(fn)) -export async function provideTestInstance(input: { directory: string; init?: Effect.Effect; fn: () => R }) { +export async function provideTestInstance(input: { + directory: string + init?: Effect.Effect + fn: (ctx: InstanceContext) => R +}) { const ctx = await runTestInstanceStore((store) => store.load({ directory: input.directory })) try { if (input.init) await testInstanceRuntime.runPromise(input.init.pipe(Effect.provideService(InstanceRef, ctx))) - return await Instance.restore(ctx, () => input.fn()) + return await instanceContext.provide(ctx, () => input.fn(ctx)) // kilocode_change } finally { - await runTestInstanceStore((store) => store.dispose(ctx)) + // kilocode_change start + await instanceContext.provide(ctx, () => + runTestInstanceStore((store) => store.dispose(ctx).pipe(Effect.provideService(InstanceRef, ctx))), + ) + // kilocode_change end } } +export async function withTestInstance(input: { directory: string; fn: (ctx: InstanceContext) => R }) { + const ctx = await runTestInstanceStore((store) => store.load({ directory: input.directory })) + return instanceContext.provide(ctx, () => input.fn(ctx)) // kilocode_change +} + export async function reloadTestInstance(input: { directory: string }) { return runTestInstanceStore((store) => store.reload(input)) } @@ -153,12 +165,20 @@ export const provideInstance = Effect.contextWith((services: Context.Context) => Effect.promise(async () => { const ctx = await runTestInstanceStore((store) => store.load({ directory })) - return Instance.restore(ctx, () => - Effect.runPromiseWith(services)(self.pipe(Effect.provideService(InstanceRef, ctx))), - ) + return Effect.runPromiseWith(services)(self.pipe(Effect.provideService(InstanceRef, ctx))) }), ) +export const provideInstanceEffect = + (directory: string) => + (self: Effect.Effect): Effect.Effect => + InstanceStore.Service.use((store) => store.provide({ directory }, self)) + +export const reloadInstance = (input: InstanceStore.LoadInput) => + InstanceStore.Service.use((store) => store.reload(input)) + +export const disposeAllInstancesEffect = InstanceStore.Service.use((store) => store.disposeAll()) + export function provideTmpdirInstance( self: (path: string) => Effect.Effect, options?: { git?: boolean; config?: Partial }, @@ -189,13 +209,8 @@ export const withTmpdirInstance = (self: Effect.Effect) => Effect.gen(function* () { const directory = yield* tmpdirScoped(options) - return yield* InstanceStore.Service.use((store) => - store.provide({ directory }, self.pipe(Effect.provideService(TestInstance, { directory }))), - ) - }).pipe( - Effect.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap))), - Effect.provide(CrossSpawnSpawner.defaultLayer), - ) + return yield* self.pipe(Effect.provideService(TestInstance, { directory }), provideInstanceEffect(directory)) + }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)) export function provideTmpdirServer( self: (input: { dir: string; llm: TestLLMServer["Service"] }) => Effect.Effect, diff --git a/packages/opencode/test/image/fixtures/picture-5mb-base64.png b/packages/opencode/test/image/fixtures/picture-5mb-base64.png new file mode 100644 index 0000000000..e8923b9e71 Binary files /dev/null and b/packages/opencode/test/image/fixtures/picture-5mb-base64.png differ diff --git a/packages/opencode/test/kilocode/agent-permission-overrides.test.ts b/packages/opencode/test/kilocode/agent-permission-overrides.test.ts index 2cd9345ba6..fe223252e5 100644 --- a/packages/opencode/test/kilocode/agent-permission-overrides.test.ts +++ b/packages/opencode/test/kilocode/agent-permission-overrides.test.ts @@ -2,7 +2,7 @@ import { afterEach, expect, test } from "bun:test" import { Effect } from "effect" import { Agent } from "../../src/agent/agent" import { Permission } from "../../src/permission" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture" function load(dir: string, fn: (svc: Agent.Interface) => Effect.Effect) { @@ -25,7 +25,7 @@ test("ask agent honors user MCP allow over generated ask rule", async () => { }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const ask = await load(tmp.path, (svc) => svc.get("ask")) @@ -44,7 +44,7 @@ test("plan agent honors user bash allow over read-only deny default", async () = }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const plan = await load(tmp.path, (svc) => svc.get("plan")) @@ -63,7 +63,7 @@ test("plan agent still hard-denies non-plan edits after user edit allow", async }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const plan = await load(tmp.path, (svc) => svc.get("plan")) diff --git a/packages/opencode/test/kilocode/archive-security.test.ts b/packages/opencode/test/kilocode/archive-security.test.ts new file mode 100644 index 0000000000..3be951c740 --- /dev/null +++ b/packages/opencode/test/kilocode/archive-security.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test" +import fs from "fs/promises" +import path from "path" +import { Archive } from "@/util/archive" +import { Process } from "@/util/process" +import { tmpdir } from "../fixture/fixture" + +test("extracts ZIP paths with PowerShell metacharacters literally", async () => { + if (process.platform !== "win32") return + + await using tmp = await tmpdir() + const source = path.join(tmp.path, "content.txt") + const archive = path.join(tmp.path, "archive'; exit 42; #.zip") + const dest = path.join(tmp.path, "dest'; exit 42; #") + const cmd = "Compress-Archive -LiteralPath $env:KILO_TEST_SOURCE -DestinationPath $env:KILO_TEST_ARCHIVE -Force" + + await fs.writeFile(source, "safe") + await Process.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", cmd], { + env: { + KILO_TEST_SOURCE: source, + KILO_TEST_ARCHIVE: archive, + }, + }) + await Archive.extractZip(archive, dest) + + expect(await fs.readFile(path.join(dest, "content.txt"), "utf8")).toBe("safe") +}) diff --git a/packages/opencode/test/kilocode/bash-permission-metadata.test.ts b/packages/opencode/test/kilocode/bash-permission-metadata.test.ts index dea0138049..fe2779230d 100644 --- a/packages/opencode/test/kilocode/bash-permission-metadata.test.ts +++ b/packages/opencode/test/kilocode/bash-permission-metadata.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" import { Effect, Layer, ManagedRuntime } from "effect" import { ShellTool } from "../../src/tool/shell" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture" import { Shell } from "../../src/shell/shell" import { SessionID, MessageID } from "../../src/session/schema" @@ -51,7 +51,7 @@ const capture = (requests: Array { test("permission prompt shows raw command without tool name prefix", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const bash = await runtime.runPromise(ShellTool.pipe(Effect.flatMap((info) => info.init()))) diff --git a/packages/opencode/test/kilocode/builtin-skills.test.ts b/packages/opencode/test/kilocode/builtin-skills.test.ts index fd3672ad70..1bf05dcd42 100644 --- a/packages/opencode/test/kilocode/builtin-skills.test.ts +++ b/packages/opencode/test/kilocode/builtin-skills.test.ts @@ -3,6 +3,7 @@ import { Effect, Layer } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import path from "path" import { Skill } from "../../src/skill" +import * as KiloSkill from "../../src/kilocode/skill-remove" import { BUILTIN_SKILLS } from "../../src/kilocode/skills/builtin" import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" @@ -40,6 +41,18 @@ it.instance( { git: true }, ) +it.instance( + "customize-opencode is protected from removal", + () => + Effect.gen(function* () { + const skill = yield* Skill.Service + const item = yield* skill.get("customize-opencode") + expect(item).toBeDefined() + expect(KiloSkill.builtin(item!.location)).toBe(true) + }), + { git: true }, +) + it.instance( "user skill overrides built-in with same name", () => diff --git a/packages/opencode/test/kilocode/cli/roll-call.test.ts b/packages/opencode/test/kilocode/cli/roll-call.test.ts index 58f73e4a5a..01174568f3 100644 --- a/packages/opencode/test/kilocode/cli/roll-call.test.ts +++ b/packages/opencode/test/kilocode/cli/roll-call.test.ts @@ -1,6 +1,6 @@ import { test, expect, describe } from "bun:test" import { Provider } from "../../../src/provider/provider" -import { formatTable, formatMarkdown, handle, isTextModel } from "../../../src/kilocode/cli/cmd/roll-call" +import { formatTable, formatMarkdown, handle, isTextModel, outputLimit } from "../../../src/kilocode/cli/cmd/roll-call" const base = { input: { text: false, audio: false, image: false, video: false, pdf: false }, @@ -100,6 +100,17 @@ describe("isTextModel", () => { }) }) +describe("outputLimit", () => { + test("honors the configured runtime output cap", () => { + const model = { + ...caps({ input: { text: true }, output: { text: true } }), + limit: { context: 100_000, input: 90_000, output: 8_000 }, + } as Provider.Model + + expect(outputLimit(model, 512)).toBe(512) + }) +}) + describe("formatMarkdown", () => { test("produces valid markdown table", () => { const rows = [ diff --git a/packages/opencode/test/kilocode/compaction-payload-recovery.test.ts b/packages/opencode/test/kilocode/compaction-payload-recovery.test.ts index dc8e46ee86..1a50a425e4 100644 --- a/packages/opencode/test/kilocode/compaction-payload-recovery.test.ts +++ b/packages/opencode/test/kilocode/compaction-payload-recovery.test.ts @@ -11,7 +11,7 @@ import { KiloCompactionPayloadRecovery } from "../../src/kilocode/session/compac import { KiloSessionCompaction } from "../../src/kilocode/session/compaction" import { Permission } from "../../src/permission" import { Plugin } from "../../src/plugin" -import { WithInstance } from "../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../src/kilocode/instance" import { ModelID, ProviderID } from "../../src/provider/schema" import { Snapshot } from "../../src/snapshot" import { LLM } from "../../src/session/llm" @@ -23,6 +23,7 @@ import { SessionCompaction } from "../../src/session/compaction" import { SessionStatus } from "../../src/session/status" import { SessionSummary } from "../../src/session/summary" import { SyncEvent } from "../../src/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import { ProviderTest } from "../fake/provider" import { tmpdir } from "../fixture/fixture" @@ -182,6 +183,7 @@ function runtime(layer: Layer.Layer, config = Config.defaultLayer) Layer.provide(summary), Layer.provide(Image.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), ) const model = ProviderTest.model({ providerID, id: modelID, limit: { context: 100_000, output: 32_000 } }) return ManagedRuntime.make( @@ -198,6 +200,7 @@ function runtime(layer: Layer.Layer, config = Config.defaultLayer) Layer.provide(config), Layer.provide(RuntimeFlags.layer()), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), ), ) } @@ -315,7 +318,7 @@ describe("KiloCompactionPayloadRecovery", () => { }), ) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await svc.create({}) diff --git a/packages/opencode/test/kilocode/config-gitignore.test.ts b/packages/opencode/test/kilocode/config-gitignore.test.ts index 59616d45b7..b80e783fcc 100644 --- a/packages/opencode/test/kilocode/config-gitignore.test.ts +++ b/packages/opencode/test/kilocode/config-gitignore.test.ts @@ -18,7 +18,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Env } from "../../src/env" import { Auth } from "../../src/auth" import { Account } from "../../src/account/account" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { Filesystem } from "../../src/util/filesystem" import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" import { tmpdir } from "../fixture/fixture" @@ -58,7 +58,7 @@ test(".gitignore in .kilo config dir includes pnpm and yarn lockfile patterns", const kilo = path.join(dir, ".kilo") await fs.mkdir(kilo, { recursive: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: dir, fn: async () => { await Effect.runPromise(Config.Service.use((svc) => svc.get()).pipe(Effect.scoped, Effect.provide(testLayer))) diff --git a/packages/opencode/test/kilocode/config-resilience.test.ts b/packages/opencode/test/kilocode/config-resilience.test.ts index e49e510bc4..a93de35099 100644 --- a/packages/opencode/test/kilocode/config-resilience.test.ts +++ b/packages/opencode/test/kilocode/config-resilience.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test" import path from "path" import { Config } from "../../src/config/config" import { AppRuntime } from "../../src/effect/app-runtime" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { Filesystem } from "../../src/util/filesystem" import { disposeAllInstances, tmpdir } from "../fixture/fixture" @@ -35,7 +35,7 @@ Valid agent prompt`, }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const cfg = await load() @@ -63,7 +63,7 @@ Broken agent prompt`, }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await load() @@ -94,7 +94,7 @@ Valid command template`, }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const cfg = await load() @@ -121,7 +121,7 @@ Broken command template`, }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await load() @@ -145,7 +145,7 @@ Broken agent`, }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await load() @@ -169,7 +169,7 @@ Broken command`, }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await load() @@ -187,7 +187,7 @@ Broken command`, }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const cfg = await load() @@ -208,7 +208,7 @@ Broken command`, }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const cfg = await load() @@ -225,7 +225,7 @@ Broken command`, config: { model: "test/model" }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await load() diff --git a/packages/opencode/test/kilocode/config-validation.test.ts b/packages/opencode/test/kilocode/config-validation.test.ts index 8cfce32259..d1c8bb7613 100644 --- a/packages/opencode/test/kilocode/config-validation.test.ts +++ b/packages/opencode/test/kilocode/config-validation.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test" import path from "path" import { ConfigValidation } from "../../src/kilocode/config-validation" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { Config } from "../../src/config/config" import { AppRuntime } from "../../src/effect/app-runtime" import { Filesystem } from "../../src/util/filesystem" @@ -20,7 +20,7 @@ describe("ConfigValidation.check", () => { const filepath = path.join(tmp.path, "src", "index.ts") await Filesystem.write(filepath, "export const x = 1") - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -32,7 +32,7 @@ describe("ConfigValidation.check", () => { const filepath = path.join(tmp.path, "kilo.json") await Filesystem.write(filepath, JSON.stringify({ model: "anthropic/claude-sonnet-4-20250514" })) - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -45,7 +45,7 @@ describe("ConfigValidation.check", () => { const filepath = path.join(tmp.path, "kilo.json") await Filesystem.write(filepath, '{ "model": "test/model" "extra": true }') - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -60,7 +60,7 @@ describe("ConfigValidation.check", () => { // Config.Info uses .strict() so unknown fields produce errors await Filesystem.write(filepath, JSON.stringify({ notAField: true })) - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -80,7 +80,7 @@ description: A test command Do something useful`, ) - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -101,7 +101,7 @@ subtask: "not-a-boolean" Do something`, ) - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -122,7 +122,7 @@ description: A helper agent You are a helpful agent.`, ) - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -135,7 +135,7 @@ You are a helpful agent.`, const filepath = path.join(tmp.path, "AGENTS.md") await Filesystem.write(filepath, "# Project agents") - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -147,7 +147,7 @@ You are a helpful agent.`, const filepath = path.join(tmp.path, ".kilo", "plans", "plan.md") await Filesystem.write(filepath, "# Plan") - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: () => check(filepath), }) @@ -172,7 +172,7 @@ Broken agent`, const filepath = path.join(tmp.path, "kilo.json") await Filesystem.write(filepath, JSON.stringify({ model: "anthropic/claude-sonnet-4-20250514" })) - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: tmp.path, fn: async () => { // Force config load to populate warnings diff --git a/packages/opencode/test/kilocode/config/config.test.ts b/packages/opencode/test/kilocode/config/config.test.ts index a1dd2fb26c..5db01fe0ab 100644 --- a/packages/opencode/test/kilocode/config/config.test.ts +++ b/packages/opencode/test/kilocode/config/config.test.ts @@ -15,7 +15,7 @@ import { ConfigMarkdown } from "../../../src/config/markdown" import { Env } from "../../../src/env" import { KiloIndexing } from "../../../src/kilocode/indexing" import { KilocodeConfig } from "../../../src/kilocode/config/config" -import { WithInstance } from "../../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../../src/kilocode/instance" import { Filesystem } from "../../../src/util/filesystem" import { disposeAllInstances, tmpdir } from "../../fixture/fixture" @@ -111,7 +111,7 @@ describe("kilocode indexing config", () => { experimental: { semantic_indexing: true, batch_tool: true }, }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const config = await load() @@ -139,7 +139,7 @@ describe("kilocode indexing config", () => { }, }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const config = await load() @@ -175,7 +175,7 @@ describe("kilocode indexing config", () => { }, }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const global = await Effect.runPromise( @@ -203,7 +203,7 @@ describe("kilocode indexing config", () => { test("creates missing project config as .kilo/kilo.jsonc", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { await saveProject({ indexing: { enabled: true } }) diff --git a/packages/opencode/test/kilocode/config/indexing-default-plugin.test.ts b/packages/opencode/test/kilocode/config/indexing-default-plugin.test.ts index 7bfdf95a23..6cdf7d4964 100644 --- a/packages/opencode/test/kilocode/config/indexing-default-plugin.test.ts +++ b/packages/opencode/test/kilocode/config/indexing-default-plugin.test.ts @@ -15,7 +15,7 @@ import { Env } from "../../../src/env" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { Filesystem } from "../../../src/util/filesystem" -import { WithInstance } from "../../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../../src/kilocode/instance" import { Npm } from "@opencode-ai/core/npm" import { disposeAllInstances, tmpdir } from "../../fixture/fixture" @@ -89,7 +89,7 @@ describe("kilocode default indexing plugin", () => { }, }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const config = await load() diff --git a/packages/opencode/test/kilocode/cost-propagation.test.ts b/packages/opencode/test/kilocode/cost-propagation.test.ts index ad7d0c8782..d68bfca694 100644 --- a/packages/opencode/test/kilocode/cost-propagation.test.ts +++ b/packages/opencode/test/kilocode/cost-propagation.test.ts @@ -7,7 +7,7 @@ import { Effect, Layer } from "effect" import { Bus } from "../../src/bus" import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" import { KiloCostPropagation } from "../../src/kilocode/session/cost-propagation" -import { Instance } from "../../src/project/instance" +import { Instance } from "../../src/kilocode/instance" import { ProviderID, ModelID } from "../../src/provider/schema" import { Session } from "../../src/session/session" import { MessageV2 } from "../../src/session/message-v2" diff --git a/packages/opencode/test/kilocode/edit-permission-filediff.test.ts b/packages/opencode/test/kilocode/edit-permission-filediff.test.ts index 598365f03d..8785d145d4 100644 --- a/packages/opencode/test/kilocode/edit-permission-filediff.test.ts +++ b/packages/opencode/test/kilocode/edit-permission-filediff.test.ts @@ -7,7 +7,7 @@ import { afterAll, afterEach, describe, test, expect } from "bun:test" import path from "path" import { Effect, Layer, ManagedRuntime } from "effect" import { EditTool } from "../../src/tool/edit" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { disposeAllInstances, tmpdir } from "../fixture/fixture" import { LSP } from "../../src/lsp/lsp" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -68,7 +68,7 @@ describe("edit tool permission filediff metadata", () => { await using tmp = await tmpdir() const filepath = path.join(tmp.path, "new.txt") - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const edit = await resolve() @@ -102,7 +102,7 @@ describe("edit tool permission filediff metadata", () => { const filepath = path.join(tmp.path, "existing.txt") await Bun.write(filepath, "line one\nline two\nline three\n") - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const edit = await resolve() @@ -136,7 +136,7 @@ describe("edit tool permission filediff metadata", () => { const filepath = path.join(tmp.path, "diff-check.txt") await Bun.write(filepath, "alpha\nbeta\ngamma\n") - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const edit = await resolve() @@ -167,7 +167,7 @@ describe("edit tool permission filediff metadata", () => { await using tmp = await tmpdir() const filepath = path.join(tmp.path, "result-new.txt") - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const edit = await resolve() @@ -195,7 +195,7 @@ describe("edit tool permission filediff metadata", () => { const filepath = path.join(tmp.path, "result-edit.txt") await Bun.write(filepath, "before\n") - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const edit = await resolve() diff --git a/packages/opencode/test/kilocode/external-directory-boundary.test.ts b/packages/opencode/test/kilocode/external-directory-boundary.test.ts index e594e5c3f7..aef72edc37 100644 --- a/packages/opencode/test/kilocode/external-directory-boundary.test.ts +++ b/packages/opencode/test/kilocode/external-directory-boundary.test.ts @@ -2,9 +2,9 @@ import { describe, expect, test } from "bun:test" import { Effect } from "effect" import path from "path" import type { Permission } from "../../src/permission" -import { Instance } from "../../src/project/instance" +import { Instance } from "../../src/kilocode/instance" import { InstanceRuntime } from "../../src/project/instance-runtime" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { SessionID, MessageID } from "../../src/session/schema" import { assertExternalDirectory } from "../../src/tool/external-directory" import type { Tool } from "../../src/tool/tool" @@ -44,7 +44,7 @@ describe("kilocode external directory boundaries", () => { const file = path.join(outer.path, "outside.txt") const { items, ctx } = asks() - await WithInstance.provide({ + await provideTestInstance({ directory: repo.path, fn: async () => { try { @@ -68,7 +68,7 @@ describe("kilocode external directory boundaries", () => { const file = path.join(outer.path, "outside-root.txt") const { items, ctx } = asks() - await WithInstance.provide({ + await provideTestInstance({ directory: root, fn: async () => { try { diff --git a/packages/opencode/test/kilocode/indexing-startup.test.ts b/packages/opencode/test/kilocode/indexing-startup.test.ts index 02e6222f52..07ec1d95e2 100644 --- a/packages/opencode/test/kilocode/indexing-startup.test.ts +++ b/packages/opencode/test/kilocode/indexing-startup.test.ts @@ -10,7 +10,7 @@ import { WorkspaceContext } from "../../src/control-plane/workspace-context" import { KiloIndexing } from "../../src/kilocode/indexing" import { indexingWarningKey } from "../../src/kilocode/indexing-warning" import { IndexingWorker } from "../../src/kilocode/indexing-worker-client" -import { WithInstance } from "../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../src/kilocode/instance" import { Server } from "../../src/server/server" import * as Log from "@opencode-ai/core/util/log" import { disposeAllInstances, tmpdir } from "../fixture/fixture" @@ -258,7 +258,7 @@ describe("indexing startup degradation", () => { await WorkspaceContext.provide({ workspaceID: workspace, fn: () => - WithInstance.provide({ + withInstanceProvide({ directory: tmp.path, fn: () => KiloIndexing.current(), }), @@ -294,7 +294,7 @@ describe("indexing startup degradation", () => { await WorkspaceContext.provide({ workspaceID: second, fn: () => - WithInstance.provide({ + withInstanceProvide({ directory: tmp.path, fn: () => KiloIndexing.warnings(), }), @@ -357,7 +357,7 @@ describe("indexing startup degradation", () => { GlobalBus.on("event", on) try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => expect(await wait(() => KiloIndexing.current(), "Complete")).toEqual(complete), }) @@ -424,7 +424,7 @@ describe("indexing startup degradation", () => { GlobalBus.on("event", on) try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { await called(init) @@ -452,7 +452,7 @@ describe("indexing startup degradation", () => { process.env["KILO_CONFIG_DIR"] = tmp.path try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const status = await wait(() => KiloIndexing.current(), "Error") @@ -478,7 +478,7 @@ describe("indexing startup degradation", () => { const init = spyOn(CodeIndexManager.prototype, "initialize").mockImplementation(() => gate.promise) try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { await called(init) @@ -500,7 +500,7 @@ describe("indexing startup degradation", () => { process.env["KILO_CONFIG_DIR"] = tmp.path const init = spyOn(CodeIndexManager.prototype, "initialize") - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const status = await wait(() => KiloIndexing.current(), "Disabled") @@ -527,7 +527,7 @@ describe("indexing startup degradation", () => { await using tmp = await tmpdir({ git: true, config: inactive }) process.env["KILO_CONFIG_DIR"] = tmp.path - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const status = await wait(() => KiloIndexing.current(), "Disabled") @@ -567,7 +567,7 @@ describe("indexing startup degradation", () => { process.env.KILO_ORG_ID = "org_123" try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { await called(init) @@ -611,7 +611,7 @@ describe("indexing startup degradation", () => { process.env.KILO_API_KEY = "kilo-token" try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { await called(init) @@ -665,7 +665,7 @@ describe("indexing startup degradation", () => { process.env.KILO_API_KEY = "kilo-token" try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { await called(init) @@ -693,7 +693,7 @@ describe("indexing startup degradation", () => { process.env.KILO_API_KEY = "kilo-token" try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { await called(init) @@ -721,7 +721,7 @@ describe("indexing startup degradation", () => { process.env.KILO_API_KEY = "kilo-token" try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { await called(init) @@ -745,7 +745,7 @@ describe("indexing startup degradation", () => { const init = spyOn(CodeIndexManager.prototype, "initialize") try { - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const status = await KiloIndexing.current() diff --git a/packages/opencode/test/kilocode/indexing-worktree.test.ts b/packages/opencode/test/kilocode/indexing-worktree.test.ts index fa42d92e1b..3042d7c9d5 100644 --- a/packages/opencode/test/kilocode/indexing-worktree.test.ts +++ b/packages/opencode/test/kilocode/indexing-worktree.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test" import { mkdir } from "node:fs/promises" import type { Config } from "../../src/config/config" import { KiloIndexing } from "../../src/kilocode/indexing" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { disposeAllInstances, tmpdir } from "../fixture/fixture" const cfg: Partial = { @@ -32,7 +32,7 @@ describe("indexing worktree disable", () => { const dir = `${tmp.path}/.kilo/worktrees/feature` await mkdir(dir, { recursive: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: dir, fn: async () => { const status = await KiloIndexing.current() @@ -52,7 +52,7 @@ describe("indexing worktree disable", () => { const dir = `${tmp.path}/.kilocode/worktrees/feature` await mkdir(dir, { recursive: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: dir, fn: async () => { const status = await KiloIndexing.current() diff --git a/packages/opencode/test/kilocode/instruction.test.ts b/packages/opencode/test/kilocode/instruction.test.ts index f0e2692e98..0d81087cb6 100644 --- a/packages/opencode/test/kilocode/instruction.test.ts +++ b/packages/opencode/test/kilocode/instruction.test.ts @@ -6,13 +6,24 @@ import { FetchHttpClient } from "effect/unstable/http" import { NodeFileSystem } from "@effect/platform-node" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { RuntimeFlags } from "../../src/effect/runtime-flags" +import { Reference } from "../../src/reference/reference" import { Instruction } from "../../src/session/instruction" import { Global } from "@opencode-ai/core/global" import { TestConfig } from "../fixture/config" import { provideInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer)) +const reference = Layer.mock(Reference.Service)({ + init: () => Effect.void, + list: () => Effect.succeed([]), + get: () => Effect.succeed(undefined), + ensure: () => Effect.void, + contains: () => Effect.succeed(false), +}) +const it = testEffect( + Layer.mergeAll(CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer, reference, RuntimeFlags.layer()), +) const configLayer = TestConfig.layer() diff --git a/packages/opencode/test/kilocode/lsp-typescript-lightweight.test.ts b/packages/opencode/test/kilocode/lsp-typescript-lightweight.test.ts index 6baee85611..4c3af1300b 100644 --- a/packages/opencode/test/kilocode/lsp-typescript-lightweight.test.ts +++ b/packages/opencode/test/kilocode/lsp-typescript-lightweight.test.ts @@ -8,7 +8,7 @@ import * as LSPServer from "../../src/lsp/server" import { TsClient } from "../../src/kilocode/ts-client" import { TsCheck } from "../../src/kilocode/ts-check" import { Flag } from "@opencode-ai/core/flag/flag" -import { Instance, type InstanceContext } from "../../src/project/instance" +import { Instance, type InstanceContext } from "../../src/kilocode/instance" import { disposeAllInstances } from "../fixture/fixture" import type { RuntimeFlags } from "../../src/effect/runtime-flags" diff --git a/packages/opencode/test/kilocode/permission/external-directory-allow.test.ts b/packages/opencode/test/kilocode/permission/external-directory-allow.test.ts index f1af05cb64..0280f9d3b2 100644 --- a/packages/opencode/test/kilocode/permission/external-directory-allow.test.ts +++ b/packages/opencode/test/kilocode/permission/external-directory-allow.test.ts @@ -10,7 +10,7 @@ import { Config } from "../../../src/config/config" import { RuntimeFlags } from "../../../src/effect/runtime-flags" import { Permission } from "../../../src/permission" import { PermissionID } from "../../../src/permission/schema" -import { WithInstance } from "../../../src/project/with-instance" +import { provideTestInstance } from "../../fixture/fixture" import { MessageID, SessionID } from "../../../src/session/schema" import { Shell } from "../../../src/shell/shell" import { Truncate } from "../../../src/tool/truncate" @@ -239,7 +239,7 @@ describe("bash external_directory access metadata", () => { test("emits read access metadata for cat external files", async () => { await using outer = await tmpdir({ init: (dir) => Bun.write(path.join(dir, "hello.txt"), "hello") }) await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const bash = await init() @@ -267,7 +267,7 @@ describe("bash external_directory access metadata", () => { withShell(item, async () => { await using outer = await tmpdir({ init: (dir) => Bun.write(path.join(dir, "hello.txt"), "hello") }) await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const bash = await init() @@ -294,7 +294,7 @@ describe("bash external_directory access metadata", () => { test("does not emit read access metadata for mutating external file commands", async () => { await using outer = await tmpdir({ init: (dir) => Bun.write(path.join(dir, "hello.txt"), "hello") }) await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const bash = await init() @@ -325,7 +325,7 @@ describe("bash external_directory access metadata", () => { test("does not emit read access metadata for mixed read and write external commands", async () => { await using outer = await tmpdir({ init: (dir) => Bun.write(path.join(dir, "hello.txt"), "hello") }) await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const bash = await init() diff --git a/packages/opencode/test/kilocode/permission/next.reply-http.test.ts b/packages/opencode/test/kilocode/permission/next.reply-http.test.ts index 20abcbdfea..d397396de7 100644 --- a/packages/opencode/test/kilocode/permission/next.reply-http.test.ts +++ b/packages/opencode/test/kilocode/permission/next.reply-http.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { WithInstance } from "../../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../../src/kilocode/instance" import { tmpdir } from "../../fixture/fixture" async function app() { @@ -11,7 +11,7 @@ describe("POST /permission/:requestID/reply", () => { test("returns 404 when requestID is not pending", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const server = await app() @@ -35,7 +35,7 @@ describe("POST /permission/:requestID/always-rules", () => { test("returns 404 when requestID is not pending", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const server = await app() diff --git a/packages/opencode/test/kilocode/plan-exit-detection.test.ts b/packages/opencode/test/kilocode/plan-exit-detection.test.ts index b2440c1a5d..363234014c 100644 --- a/packages/opencode/test/kilocode/plan-exit-detection.test.ts +++ b/packages/opencode/test/kilocode/plan-exit-detection.test.ts @@ -6,8 +6,8 @@ import path from "path" import { Identifier } from "../../src/id/id" import { SessionID, MessageID, PartID } from "../../src/session/schema" import { ModelID, ProviderID } from "../../src/provider/schema" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { Instance } from "../../src/kilocode/instance" +import { provide as withInstanceProvide } from "../../src/kilocode/instance" import { PlanFollowup } from "../../src/kilocode/plan-followup" import { KiloSessionPrompt } from "../../src/kilocode/session/prompt" import { makeRuntime } from "../../src/effect/run-service" @@ -56,7 +56,7 @@ const model = { async function withInstance(fn: () => Promise) { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ directory: tmp.path, fn }) + await withInstanceProvide({ directory: tmp.path, fn }) } async function seed(input: { diff --git a/packages/opencode/test/kilocode/plan-file.test.ts b/packages/opencode/test/kilocode/plan-file.test.ts index 8d3fbf8a67..b1a9c51dac 100644 --- a/packages/opencode/test/kilocode/plan-file.test.ts +++ b/packages/opencode/test/kilocode/plan-file.test.ts @@ -3,8 +3,8 @@ import { Effect, Layer, ManagedRuntime } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" import { PlanFile } from "../../src/kilocode/plan-file" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { Instance } from "../../src/kilocode/instance" +import { provide as withInstanceProvide } from "../../src/kilocode/instance" import { Session } from "../../src/session/session" import { MessageID } from "../../src/session/schema" import { PlanExitTool } from "../../src/tool/plan" @@ -26,7 +26,7 @@ async function init() { describe("PlanFile", () => { test("plan_exit accepts custom paths from plan agent", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await rt.runPromise(Session.Service.use((svc) => svc.create({}))) @@ -54,7 +54,7 @@ describe("PlanFile", () => { test("rejects custom plan paths outside the worktree", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { expect(PlanFile.resolve("../../etc/shadow", Instance.current)).toBeUndefined() diff --git a/packages/opencode/test/kilocode/plan-followup.test.ts b/packages/opencode/test/kilocode/plan-followup.test.ts index d0835591eb..06664e7c21 100644 --- a/packages/opencode/test/kilocode/plan-followup.test.ts +++ b/packages/opencode/test/kilocode/plan-followup.test.ts @@ -7,8 +7,8 @@ import { Identifier } from "../../src/id/id" import { SessionID, MessageID, PartID } from "../../src/session/schema" import { ModelID, ProviderID } from "../../src/provider/schema" import { formatTodos, generateHandover, PlanFollowup, PlanFollowupRuntime } from "../../src/kilocode/plan-followup" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { Instance } from "../../src/kilocode/instance" +import { provideTestInstance } from "../fixture/fixture" import { Provider } from "../../src/provider/provider" import { Question } from "../../src/question" import { Session } from "../../src/session/session" @@ -90,7 +90,7 @@ const savedKey = `${saved.providerID}/${saved.modelID}` async function withInstance(fn: () => Promise) { await using tmp = await tmpdir({ git: true }) await fs.rm(statePath, { force: true }).catch(() => {}) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await fs.rm(statePath, { force: true }).catch(() => {}) @@ -724,12 +724,12 @@ describe("plan follow-up", () => { const dir = other.path - const seeded = await WithInstance.provide({ + const seeded = await provideTestInstance({ directory: dir, fn: async () => seed({ text: "1. Add API\n2. Add tests" }), }) - const before = await WithInstance.provide({ + const before = await provideTestInstance({ directory: dir, fn: async () => sessions(), }) @@ -750,7 +750,7 @@ describe("plan follow-up", () => { }) await expect(pending).resolves.toBe("break") - const after = await WithInstance.provide({ + const after = await provideTestInstance({ directory: dir, fn: async () => sessions(), }) @@ -764,7 +764,7 @@ describe("plan follow-up", () => { expect(next?.parentID).toBeUndefined() if (next) { - const planPath = await WithInstance.provide({ + const planPath = await provideTestInstance({ directory: dir, fn: async () => Session.plan(await store.get(seeded.sessionID), Instance.current), }) diff --git a/packages/opencode/test/kilocode/project-config-update.test.ts b/packages/opencode/test/kilocode/project-config-update.test.ts index 70ba451e2c..cfd62dfb71 100644 --- a/packages/opencode/test/kilocode/project-config-update.test.ts +++ b/packages/opencode/test/kilocode/project-config-update.test.ts @@ -12,7 +12,7 @@ import { Auth } from "../../src/auth" import { Account } from "../../src/account/account" import { Env } from "../../src/env" import { Npm } from "@opencode-ai/core/npm" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { Filesystem } from "../../src/util/filesystem" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { tmpdir } from "../fixture/fixture" @@ -56,7 +56,7 @@ async function writeConfig(dir: string, config: unknown) { test("project config update creates .kilo/kilo.jsonc and reloads it", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await save({ model: "updated/model" } as any) @@ -72,7 +72,7 @@ test("project config update creates .kilo/kilo.jsonc and reloads it", async () = test("project config update skips empty delete-only writes when no config exists", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await save({ provider: { missing: null } } as any) @@ -86,7 +86,7 @@ test("project config update prefers existing root kilo.json", async () => { await using tmp = await tmpdir() await writeConfig(tmp.path, { username: "alice" }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { await save({ model: "updated/model" } as any) @@ -105,7 +105,7 @@ test("project config update patches ancestor .kilo/kilo.json from nested directo await fs.mkdir(path.join(tmp.path, ".kilo"), { recursive: true }) await writeConfig(path.join(tmp.path, ".kilo"), { username: "alice" }) - await WithInstance.provide({ + await provideTestInstance({ directory: child, fn: async () => { await save({ model: "updated/model" } as any) diff --git a/packages/opencode/test/kilocode/project-id.test.ts b/packages/opencode/test/kilocode/project-id.test.ts index 8f9198c4ff..d93dc7a85d 100644 --- a/packages/opencode/test/kilocode/project-id.test.ts +++ b/packages/opencode/test/kilocode/project-id.test.ts @@ -2,7 +2,7 @@ import { test, expect, describe } from "bun:test" import { tmpdir } from "../fixture/fixture" import path from "path" import fs from "fs/promises" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { getKiloProjectId } from "../../src/kilocode/project-id" describe("project-id", () => { @@ -16,7 +16,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -33,7 +33,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -49,7 +49,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -65,7 +65,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -82,7 +82,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -112,7 +112,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -136,7 +136,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -158,7 +158,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -182,7 +182,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -208,7 +208,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -234,7 +234,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -258,7 +258,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -284,7 +284,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -297,7 +297,7 @@ describe("project-id", () => { test("returns undefined when no config and no git origin", async () => { await using tmp = await tmpdir({ git: true }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -308,7 +308,7 @@ describe("project-id", () => { test("returns undefined for non-git directory", async () => { await using tmp = await tmpdir() - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -327,7 +327,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -354,7 +354,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -373,12 +373,12 @@ describe("project-id", () => { }, }) - const id1 = await WithInstance.provide({ + const id1 = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) - const id2 = await WithInstance.provide({ + const id2 = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -397,7 +397,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -420,7 +420,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) @@ -444,7 +444,7 @@ describe("project-id", () => { }, }) - const id = await WithInstance.provide({ + const id = await provideTestInstance({ directory: tmp.path, fn: () => getKiloProjectId(), }) diff --git a/packages/opencode/test/kilocode/security-github.test.ts b/packages/opencode/test/kilocode/security-github.test.ts new file mode 100644 index 0000000000..a807fb746b --- /dev/null +++ b/packages/opencode/test/kilocode/security-github.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "bun:test" +import { GitHubSecurity } from "@/kilocode/security/github" + +describe("GitHubSecurity.attachment", () => { + test("accepts canonical GitHub attachment URLs", () => { + expect( + GitHubSecurity.attachment("https://github.com/user-attachments/assets/123e4567-e89b-12d3-a456-426614174000"), + ).toBe("https://github.com/user-attachments/assets/123e4567-e89b-12d3-a456-426614174000") + expect(GitHubSecurity.attachment("https://github.com/user-attachments/files/12345/report%20final.txt")).toBe( + "https://github.com/user-attachments/files/12345/report%20final.txt", + ) + }) + + test.each([ + "https://github.com/user-attachments/assets/../../settings/profile", + "https://github.com/user-attachments/assets/%2e%2e/%2e%2e/settings/profile", + "https://github.com/user-attachments/files/12345/%2Fsettings", + "https://github.com/user-attachments/files/12345/folder/report.txt", + "https://github.com/user-attachments/assets/not-a-uuid", + "https://github.com/user-attachments/assets/123e4567-e89b-12d3-a456-426614174000?download=1", + "https://github.com/user-attachments/assets/123e4567-e89b-12d3-a456-426614174000#fragment", + "https://example.com/user-attachments/assets/123e4567-e89b-12d3-a456-426614174000", + "http://github.com/user-attachments/assets/123e4567-e89b-12d3-a456-426614174000", + "not a URL", + ])("rejects non-canonical attachment URL %s", (url) => { + expect(GitHubSecurity.attachment(url)).toBeUndefined() + }) +}) diff --git a/packages/opencode/test/kilocode/semantic-search.test.ts b/packages/opencode/test/kilocode/semantic-search.test.ts index 2e69a77efa..2b6f6efc79 100644 --- a/packages/opencode/test/kilocode/semantic-search.test.ts +++ b/packages/opencode/test/kilocode/semantic-search.test.ts @@ -4,7 +4,7 @@ import { Effect, Layer, ManagedRuntime } from "effect" import { Agent } from "../../src/agent/agent" import { SemanticSearchTool } from "../../src/kilocode/tool/semantic-search" import { KiloIndexing } from "../../src/kilocode/indexing" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture" import type { Permission } from "../../src/permission" import { SessionID, MessageID } from "../../src/session/schema" @@ -48,7 +48,7 @@ describe("tool.semantic_search", () => { test("asks permission and forwards normalized relative path to indexing search", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const requests: Array> = [] @@ -89,7 +89,7 @@ describe("tool.semantic_search", () => { test("searches entire workspace when path is omitted", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const search = spyOn(KiloIndexing, "search").mockResolvedValue([]) @@ -110,7 +110,7 @@ describe("tool.semantic_search", () => { test("formats and normalizes search results", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const search = spyOn(KiloIndexing, "search").mockResolvedValue([ @@ -166,7 +166,7 @@ describe("tool.semantic_search", () => { test("rejects paths outside the workspace", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const search = spyOn(KiloIndexing, "search").mockResolvedValue([]) diff --git a/packages/opencode/test/kilocode/server/permission-allow-everything.test.ts b/packages/opencode/test/kilocode/server/permission-allow-everything.test.ts index 2842095b3b..e00ceb824e 100644 --- a/packages/opencode/test/kilocode/server/permission-allow-everything.test.ts +++ b/packages/opencode/test/kilocode/server/permission-allow-everything.test.ts @@ -7,7 +7,7 @@ import * as Config from "../../../src/config/config" import { AllowEverythingPermission } from "../../../src/kilocode/permission/allow-everything" import { Permission } from "../../../src/permission" import { PermissionID } from "../../../src/permission/schema" -import { WithInstance } from "../../../src/project/with-instance" +import { provideTestInstance } from "../../fixture/fixture" import { Server } from "../../../src/server/server" import { Session } from "../../../src/session/session" import { provideTmpdirInstance, tmpdir } from "../../fixture/fixture" @@ -48,7 +48,7 @@ const wait = () => describe("AllowEverythingPermission", () => { test("handles disable requests through the HTTP endpoint", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const enable = await Server.Default().app.request("/permission/allow-everything", { diff --git a/packages/opencode/test/kilocode/session-compaction-cap.test.ts b/packages/opencode/test/kilocode/session-compaction-cap.test.ts index f38dd97e39..6e64030551 100644 --- a/packages/opencode/test/kilocode/session-compaction-cap.test.ts +++ b/packages/opencode/test/kilocode/session-compaction-cap.test.ts @@ -47,6 +47,7 @@ import { Todo } from "../../src/session/todo" import { Skill } from "../../src/skill" import { Snapshot } from "../../src/snapshot" import { SyncEvent } from "../../src/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import { ToolRegistry } from "../../src/tool/registry" import { Truncate } from "../../src/tool/truncate" import * as Log from "@opencode-ai/core/util/log" @@ -145,6 +146,7 @@ function makeHttp() { mcp, AppFileSystem.defaultLayer, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, Reference.defaultLayer, status, ).pipe(Layer.provideMerge(infra)) diff --git a/packages/opencode/test/kilocode/session-compaction-chunks.test.ts b/packages/opencode/test/kilocode/session-compaction-chunks.test.ts index 8c5ecf1205..fad05cd7ed 100644 --- a/packages/opencode/test/kilocode/session-compaction-chunks.test.ts +++ b/packages/opencode/test/kilocode/session-compaction-chunks.test.ts @@ -8,7 +8,7 @@ import { RuntimeFlags } from "../../src/effect/runtime-flags" import { Image } from "../../src/image/image" import { Permission } from "../../src/permission" import { Plugin } from "../../src/plugin" -import { WithInstance } from "../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../src/kilocode/instance" import { ModelID, ProviderID } from "../../src/provider/schema" import { Snapshot } from "../../src/snapshot" import { KiloCompactionChunks } from "../../src/kilocode/session/compaction-chunks" @@ -23,6 +23,7 @@ import { Session as SessionNs } from "../../src/session/session" import { SessionStatus } from "../../src/session/status" import { SessionSummary } from "../../src/session/summary" import { SyncEvent } from "../../src/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import { ProviderTest } from "../fake/provider" import { tmpdir } from "../fixture/fixture" @@ -208,6 +209,7 @@ function runtime(layer: Layer.Layer, context = 7_000) { Layer.provide(summary), Layer.provide(Image.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), ) const model = ProviderTest.model({ providerID, id: modelID, limit: { context, output: 1_000 } }) return ManagedRuntime.make( @@ -220,6 +222,7 @@ function runtime(layer: Layer.Layer, context = 7_000) { Layer.provide(Agent.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), Layer.provide(RuntimeFlags.layer()), Layer.provide(status), Layer.provide(bus), @@ -286,6 +289,7 @@ function fakeRuntime() { Layer.provide(Agent.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), Layer.provide(RuntimeFlags.layer()), Layer.provide(bus), Layer.provide( @@ -305,6 +309,7 @@ function liveRuntime(layer: Layer.Layer, context = 10_000) { Layer.provide(summary), Layer.provide(Image.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), ) const model = ProviderTest.model({ providerID, id: modelID, limit: { context, output: 1_000 } }) return ManagedRuntime.make( @@ -317,6 +322,7 @@ function liveRuntime(layer: Layer.Layer, context = 10_000) { Layer.provide(Agent.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(EventV2Bridge.defaultLayer), Layer.provide(RuntimeFlags.layer()), Layer.provide(status), Layer.provide(bus), @@ -367,7 +373,7 @@ describe("KiloCompactionChunks", () => { test("falls back to chunk workers after the first compaction overflows", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await svc.create({}) @@ -421,7 +427,7 @@ describe("KiloCompactionChunks", () => { test("uses chunk fallback before sending oversized normal compaction", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await svc.create({}) @@ -467,7 +473,7 @@ describe("KiloCompactionChunks", () => { test("uses a worker even when fallback selection produces one oversized chunk", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await svc.create({}) @@ -519,7 +525,7 @@ describe("KiloCompactionChunks", () => { test("serializes oversized fallback chunks before summarizing", async () => { await using tmp = await tmpdir() - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await svc.create({}) @@ -564,7 +570,7 @@ describe("KiloCompactionChunks", () => { test("caps worker output budget below oversized model output limit", async () => { const { rt, calls, outputs } = fakeRuntime() await using tmp = await tmpdir() - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await svc.create({}) @@ -612,7 +618,7 @@ describe("KiloCompactionChunks", () => { stub.push(reply("replay summary", (input) => calls.push(JSON.stringify(input.messages)))) await using tmp = await tmpdir() - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await svc.create({}) diff --git a/packages/opencode/test/kilocode/session-fork-remap.test.ts b/packages/opencode/test/kilocode/session-fork-remap.test.ts index 77c0618a40..38becde2f8 100644 --- a/packages/opencode/test/kilocode/session-fork-remap.test.ts +++ b/packages/opencode/test/kilocode/session-fork-remap.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, test } from "bun:test" import { Effect } from "effect" import { createKiloClient } from "@kilocode/sdk/v2/client" -import { WithInstance } from "../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../src/kilocode/instance" import { Server } from "../../src/server/server" import { Session } from "../../src/session/session" import { MessageV2 } from "../../src/session/message-v2" @@ -98,7 +98,7 @@ describe("Session.fork cost accounting", () => { "forked sessions start with zero cost", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const original = await sessions.create({ title: "original" }) @@ -140,7 +140,7 @@ describe("Session.fork task detachment", () => { "keeps completed task outcomes without cloning child sessions", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const parent = await sessions.create({ title: "parent" }) @@ -199,7 +199,7 @@ describe("Session.fork task detachment", () => { "turns copied running tasks into terminal historical errors", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const parent = await sessions.create({ title: "parent" }) @@ -241,7 +241,7 @@ describe("Session.fork task detachment", () => { "detaches pending and errored task references", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const parent = await sessions.create({ title: "parent" }) @@ -310,7 +310,7 @@ describe("Session.fork task detachment", () => { Flag.KILO_EXPERIMENTAL_WORKSPACES = true try { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const parent = await sessions.create({ title: "parent" }) @@ -359,7 +359,7 @@ describe("Session.fork task detachment", () => { "does not alter non-task parts", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const parent = await sessions.create({ title: "parent" }) diff --git a/packages/opencode/test/kilocode/session-list.test.ts b/packages/opencode/test/kilocode/session-list.test.ts index 519baa7263..0dc998fde7 100644 --- a/packages/opencode/test/kilocode/session-list.test.ts +++ b/packages/opencode/test/kilocode/session-list.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test" import { Effect } from "effect" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { ProjectTable } from "../../src/project/project.sql" import { ProjectID } from "../../src/project/schema" import { Session } from "../../src/session/session" @@ -18,7 +18,7 @@ afterEach(async () => { describe("Kilo Session.list", () => { test("includes directory matches from legacy project ids", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const session = await Effect.runPromise( diff --git a/packages/opencode/test/kilocode/session-processor-empty-tool-calls.test.ts b/packages/opencode/test/kilocode/session-processor-empty-tool-calls.test.ts index b318976bca..1432ca2b0c 100644 --- a/packages/opencode/test/kilocode/session-processor-empty-tool-calls.test.ts +++ b/packages/opencode/test/kilocode/session-processor-empty-tool-calls.test.ts @@ -21,6 +21,7 @@ import { SessionStatus } from "../../src/session/status" import { SessionSummary } from "../../src/session/summary" import { Snapshot } from "../../src/snapshot" import { SyncEvent } from "../../src/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import { KiloSessionProcessor } from "../../src/kilocode/session/processor" import * as Log from "@opencode-ai/core/util/log" import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" @@ -107,6 +108,7 @@ const deps = Layer.mergeAll( SessionSummary.defaultLayer, Image.defaultLayer, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, status, llm, ).pipe(Layer.provideMerge(infra)) diff --git a/packages/opencode/test/kilocode/session-processor-network-offline.test.ts b/packages/opencode/test/kilocode/session-processor-network-offline.test.ts index abdb5fc55d..a5833156c9 100644 --- a/packages/opencode/test/kilocode/session-processor-network-offline.test.ts +++ b/packages/opencode/test/kilocode/session-processor-network-offline.test.ts @@ -22,6 +22,7 @@ import { SessionStatus } from "../../src/session/status" import { SessionSummary } from "../../src/session/summary" import { Snapshot } from "../../src/snapshot" import { SyncEvent } from "../../src/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import * as Log from "@opencode-ai/core/util/log" import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" @@ -106,6 +107,7 @@ const deps = Layer.mergeAll( SessionSummary.defaultLayer, Image.defaultLayer, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, status, llm, ).pipe(Layer.provideMerge(infra)) diff --git a/packages/opencode/test/kilocode/session-processor-retry-limit.test.ts b/packages/opencode/test/kilocode/session-processor-retry-limit.test.ts index 827e9c0cf3..4f07c11a57 100644 --- a/packages/opencode/test/kilocode/session-processor-retry-limit.test.ts +++ b/packages/opencode/test/kilocode/session-processor-retry-limit.test.ts @@ -29,6 +29,7 @@ import { SessionStatus } from "../../src/session/status" import { SessionSummary } from "../../src/session/summary" import { Snapshot } from "../../src/snapshot" import { SyncEvent } from "../../src/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import * as Log from "@opencode-ai/core/util/log" import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" @@ -119,6 +120,7 @@ const deps = Layer.mergeAll( SessionSummary.defaultLayer, Image.defaultLayer, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, status, llm, ).pipe(Layer.provideMerge(infra)) diff --git a/packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts b/packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts index 22c1cae000..8bb3b54c3c 100644 --- a/packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts +++ b/packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts @@ -44,6 +44,7 @@ import { Todo } from "../../src/session/todo" import { Skill } from "../../src/skill" import { Snapshot } from "../../src/snapshot" import { SyncEvent } from "../../src/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import { ToolRegistry } from "../../src/tool/registry" import { Truncate } from "../../src/tool/truncate" import * as Log from "@opencode-ai/core/util/log" @@ -139,6 +140,7 @@ function makeHttp() { AppFileSystem.defaultLayer, Reference.defaultLayer, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, status, ).pipe(Layer.provideMerge(infra)) const question = Question.layer.pipe(Layer.provideMerge(deps)) diff --git a/packages/opencode/test/kilocode/session-prompt-permission-refresh.test.ts b/packages/opencode/test/kilocode/session-prompt-permission-refresh.test.ts index 168977c9ac..76c4997f2a 100644 --- a/packages/opencode/test/kilocode/session-prompt-permission-refresh.test.ts +++ b/packages/opencode/test/kilocode/session-prompt-permission-refresh.test.ts @@ -38,6 +38,7 @@ import { Todo } from "../../src/session/todo" import { Skill } from "../../src/skill" import { Snapshot } from "../../src/snapshot" import { SyncEvent } from "../../src/sync" +import { EventV2Bridge } from "@/event-v2-bridge" import { Ripgrep } from "../../src/file/ripgrep" import { ToolRegistry } from "../../src/tool/registry" import { Truncate } from "../../src/tool/truncate" @@ -133,6 +134,7 @@ function makeHttp() { AppFileSystem.defaultLayer, Reference.defaultLayer, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, status, ).pipe(Layer.provideMerge(infra)) const question = Question.layer.pipe(Layer.provideMerge(deps)) diff --git a/packages/opencode/test/kilocode/session-prompt-queue.test.ts b/packages/opencode/test/kilocode/session-prompt-queue.test.ts index 4e6c57ebaf..9cfbbb9f8a 100644 --- a/packages/opencode/test/kilocode/session-prompt-queue.test.ts +++ b/packages/opencode/test/kilocode/session-prompt-queue.test.ts @@ -9,7 +9,7 @@ import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue" import { Suggestion } from "../../src/kilocode/suggestion" import { ModelID, ProviderID } from "../../src/provider/schema" import { InstanceStore } from "../../src/project/instance-store" -import { WithInstance } from "../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../src/kilocode/instance" import { Session } from "../../src/session/session" import { MessageV2 } from "../../src/session/message-v2" import { SessionCompaction } from "../../src/session/compaction" @@ -275,7 +275,7 @@ describe("session prompt queue", () => { // scope() hides that marker, runLoop never processes the compaction task and // instead retries the same oversized request until compaction is exhausted. await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const session = await sessions.create({ title: "Queued compaction regression" }) @@ -444,7 +444,7 @@ describe("session prompt queue", () => { }, }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => scoped(tmp.path, async (prompt) => { @@ -656,7 +656,7 @@ describe("session prompt queue", () => { }, }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => scoped(tmp.path, async (prompt) => { @@ -722,7 +722,7 @@ describe("session prompt queue", () => { const dismissed = Promise.withResolvers() await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => scoped(tmp.path, async (prompt) => { @@ -771,7 +771,7 @@ describe("session prompt queue", () => { // hasFollowup=true and reject synchronously, before any pending entry or // Shown event is published. await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await withInstanceProvide({ directory: tmp.path, fn: async () => { const sessionID = SessionID.make("ses_auto_suggestion") diff --git a/packages/opencode/test/kilocode/session/instruction-substitution.test.ts b/packages/opencode/test/kilocode/session/instruction-substitution.test.ts index 98cc02243a..d466808967 100644 --- a/packages/opencode/test/kilocode/session/instruction-substitution.test.ts +++ b/packages/opencode/test/kilocode/session/instruction-substitution.test.ts @@ -5,6 +5,8 @@ import { FetchHttpClient } from "effect/unstable/http" import { NodeFileSystem } from "@effect/platform-node" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { RuntimeFlags } from "../../../src/effect/runtime-flags" +import { Reference } from "../../../src/reference/reference" import { Instruction } from "../../../src/session/instruction" import { MessageID } from "../../../src/session/schema" import { Global } from "@opencode-ai/core/global" @@ -12,7 +14,16 @@ import { provideTmpdirInstance } from "../../fixture/fixture" import { testEffect } from "../../lib/effect" import { TestConfig } from "../../fixture/config" -const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer)) +const reference = Layer.mock(Reference.Service)({ + init: () => Effect.void, + list: () => Effect.succeed([]), + get: () => Effect.succeed(undefined), + ensure: () => Effect.void, + contains: () => Effect.succeed(false), +}) +const it = testEffect( + Layer.mergeAll(CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer, reference, RuntimeFlags.layer()), +) const configLayer = TestConfig.layer() diff --git a/packages/opencode/test/kilocode/session/platform-attribution.test.ts b/packages/opencode/test/kilocode/session/platform-attribution.test.ts index 51ecd66564..997a130257 100644 --- a/packages/opencode/test/kilocode/session/platform-attribution.test.ts +++ b/packages/opencode/test/kilocode/session/platform-attribution.test.ts @@ -5,7 +5,7 @@ import { Session as SessionNs } from "@/session/session" import { AppRuntime } from "../../../src/effect/app-runtime" import { Bus } from "../../../src/bus" import { KiloSession } from "../../../src/kilocode/session" -import { WithInstance } from "../../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../../src/kilocode/instance" import { MessageV2 } from "../../../src/session/message-v2" import { MessageID, PartID, type SessionID } from "../../../src/session/schema" @@ -30,7 +30,7 @@ function updatePart(part: T) { describe("session platform attribution", () => { test("child sessions inherit the root platform override", async () => { - await WithInstance.provide({ + await withInstanceProvide({ directory: projectRoot, fn: async () => { const root = await create({ platform: "agent-manager" }) @@ -49,7 +49,7 @@ describe("session platform attribution", () => { }) test("child sessions expose parent and root lineage", async () => { - await WithInstance.provide({ + await withInstanceProvide({ directory: projectRoot, fn: async () => { const root = await create({}) @@ -71,7 +71,7 @@ describe("step-finish token propagation via Bus event", () => { test( "non-zero tokens propagate through PartUpdated event", async () => { - await WithInstance.provide({ + await withInstanceProvide({ directory: projectRoot, fn: async () => { const info = await create({}) diff --git a/packages/opencode/test/kilocode/session/session.test.ts b/packages/opencode/test/kilocode/session/session.test.ts index 323b7091b9..166e111122 100644 --- a/packages/opencode/test/kilocode/session/session.test.ts +++ b/packages/opencode/test/kilocode/session/session.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Session as SessionNs } from "@/session/session" import { Bus } from "../../../src/bus" import * as Log from "@opencode-ai/core/util/log" -import { WithInstance } from "../../../src/project/with-instance" +import { provide as withInstanceProvide } from "../../../src/kilocode/instance" import { AppRuntime } from "../../../src/effect/app-runtime" import { RuntimeFlags } from "../../../src/effect/runtime-flags" import { Effect } from "effect" @@ -27,7 +27,7 @@ function remove(id: SessionID) { describe("session.created event", () => { test("should emit session.created event when session is created", async () => { - await WithInstance.provide({ + await withInstanceProvide({ directory: projectRoot, fn: async () => { let eventReceived = false @@ -62,7 +62,7 @@ describe("session.created event", () => { const previous = process.env.KILO_EXPERIMENTAL_WORKSPACES delete process.env.KILO_EXPERIMENTAL_WORKSPACES try { - await WithInstance.provide({ + await withInstanceProvide({ directory: projectRoot, fn: async () => { const flags = AppRuntime.runSync(Effect.service(RuntimeFlags.Service)) @@ -103,7 +103,7 @@ describe("Session", () => { test("remove works without an instance", async () => { await using tmp = await tmpdir({ git: true }) - const info = await WithInstance.provide({ + const info = await withInstanceProvide({ directory: tmp.path, fn: () => create({ title: "remove-without-instance" }), }) diff --git a/packages/opencode/test/kilocode/skill-remove.test.ts b/packages/opencode/test/kilocode/skill-remove.test.ts new file mode 100644 index 0000000000..1a259d769a --- /dev/null +++ b/packages/opencode/test/kilocode/skill-remove.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, test } from "bun:test" +import path from "node:path" +import { Global } from "@opencode-ai/core/global" +import { target } from "../../src/kilocode/skill-remove" + +const info = (location: string) => ({ + name: "synthetic", + description: "Synthetic skill used for path validation.", + location, + content: "synthetic", +}) + +describe("skill removal target", () => { + test("rejects the canonical built-in location", () => { + expect(() => target("builtin", [info("builtin")])).toThrow("cannot remove built-in skill") + }) + + test("rejects the legacy customize-opencode built-in location", () => { + expect(() => target("", [info("")])).toThrow("cannot remove built-in skill") + }) + + test("rejects locations that are not in the active skill registry", () => { + const location = path.join(path.parse(process.cwd()).root, "__kilo_synthetic__", "SKILL.md") + expect(() => target(location, [])).toThrow("skill not found in registry") + }) + + test("rejects relative registered locations", () => { + const location = path.join("synthetic", "SKILL.md") + expect(() => target(location, [info(location)])).toThrow("skill location must be absolute") + }) + + test("rejects registered locations that are not manifests", () => { + const location = path.join(path.parse(process.cwd()).root, "__kilo_synthetic__", "skill") + expect(() => target(location, [info(location)])).toThrow("skill location must reference SKILL.md") + }) + + test("rejects URL-backed cache entries", () => { + const location = path.join(Global.Path.cache, "skills", "synthetic", "SKILL.md") + expect(() => target(location, [info(location)])).toThrow("remove URL-backed skills from configuration") + }) + + test("returns only the registered skill manifest", () => { + const location = path.join(path.parse(process.cwd()).root, "__kilo_synthetic__", "skill", "SKILL.md") + expect(target(location, [info(location)])).toBe(location) + }) +}) diff --git a/packages/opencode/test/kilocode/snapshot-cache.test.ts b/packages/opencode/test/kilocode/snapshot-cache.test.ts index 6fb9e86dbb..e3e7bbba9e 100644 --- a/packages/opencode/test/kilocode/snapshot-cache.test.ts +++ b/packages/opencode/test/kilocode/snapshot-cache.test.ts @@ -2,7 +2,7 @@ import { test, expect } from "bun:test" import { $ } from "bun" import { Effect } from "effect" import { Snapshot } from "../../src/snapshot" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { Filesystem } from "../../src/util/filesystem" import * as Log from "@opencode-ai/core/util/log" import { tmpdir } from "../fixture/fixture" @@ -27,7 +27,7 @@ function run(body: (snapshot: Snapshot.Interface) => Effect.Effect) { test("diffFull returns cached result for same hash pair", async () => { await using tmp = await bootstrap() - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: () => run((snapshot) => @@ -53,7 +53,7 @@ test("diffFull returns cached result for same hash pair", async () => { test("diffFull returns empty array when from === to", async () => { await using tmp = await bootstrap() - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: () => run((snapshot) => @@ -70,7 +70,7 @@ test("diffFull returns empty array when from === to", async () => { test("diffFull concurrent calls for same pair share one result", async () => { await using tmp = await bootstrap() - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: () => run((snapshot) => diff --git a/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts b/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts index 4dc29a388e..863ba23295 100644 --- a/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts +++ b/packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts @@ -15,7 +15,7 @@ import { test, expect, afterEach, mock } from "bun:test" import { $ } from "bun" import { Effect, Fiber } from "effect" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance } from "../fixture/fixture" import { Server } from "../../src/server/server" import { Session } from "../../src/session/session" import { Snapshot } from "../../src/snapshot" @@ -52,7 +52,7 @@ test("pathological diffFull workload finishes quickly and does not block abort", }, }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: () => run((snapshot) => diff --git a/packages/opencode/test/kilocode/snapshot-seed.test.ts b/packages/opencode/test/kilocode/snapshot-seed.test.ts index 375324ae63..84813cbcf7 100644 --- a/packages/opencode/test/kilocode/snapshot-seed.test.ts +++ b/packages/opencode/test/kilocode/snapshot-seed.test.ts @@ -6,7 +6,7 @@ import { Effect } from "effect" import { Global } from "@opencode-ai/core/global" import { Hash } from "@opencode-ai/core/util/hash" import { Snapshot } from "../../src/snapshot" -import { Instance } from "../../src/project/instance" +import { Instance } from "../../src/kilocode/instance" import { Filesystem } from "../../src/util/filesystem" import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture" diff --git a/packages/opencode/test/kilocode/suggestion/auto-dismiss.test.ts b/packages/opencode/test/kilocode/suggestion/auto-dismiss.test.ts index b5c95ec97e..5318a2786a 100644 --- a/packages/opencode/test/kilocode/suggestion/auto-dismiss.test.ts +++ b/packages/opencode/test/kilocode/suggestion/auto-dismiss.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" import { Effect } from "effect" import { KiloSessionPromptQueue } from "../../../src/kilocode/session/prompt-queue" import { Suggestion } from "../../../src/kilocode/suggestion" -import { WithInstance } from "../../../src/project/with-instance" +import { provideTestInstance } from "../../fixture/fixture" import { MessageID, SessionID } from "../../../src/session/schema" import { tmpdir } from "../../fixture/fixture" @@ -13,7 +13,7 @@ describe("Suggestion.show auto-dismiss on queued followup", () => { // hasFollowup check rejects with DismissedError before any pending entry // is registered or a Shown event is published. await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const sessionID = SessionID.make("ses_auto_show") diff --git a/packages/opencode/test/kilocode/suggestion/suggestion.test.ts b/packages/opencode/test/kilocode/suggestion/suggestion.test.ts index 524cd17fa6..aeeb85d357 100644 --- a/packages/opencode/test/kilocode/suggestion/suggestion.test.ts +++ b/packages/opencode/test/kilocode/suggestion/suggestion.test.ts @@ -3,7 +3,7 @@ import { Effect } from "effect" import { Telemetry } from "@kilocode/kilo-telemetry" import { Command } from "../../../src/command" import { localReviewUncommittedCommand } from "../../../src/kilocode/review/command" -import { WithInstance } from "../../../src/project/with-instance" +import { provideTestInstance } from "../../fixture/fixture" import { Suggestion } from "../../../src/kilocode/suggestion" import { resolvePrompt } from "../../../src/kilocode/suggestion/tool" import { SessionID } from "../../../src/session/schema" @@ -27,7 +27,7 @@ describe("suggestion", () => { test("show adds pending request with blocking flag", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const pending = Suggestion.show({ @@ -50,7 +50,7 @@ describe("suggestion", () => { test("accept resolves selected action and removes pending request", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const ask = Suggestion.show({ @@ -77,7 +77,7 @@ describe("suggestion", () => { test("accept tracks suggestion telemetry with parsed slash command", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const track = spyOn(Telemetry, "trackSuggestionAccepted") @@ -106,7 +106,7 @@ describe("suggestion", () => { test("show tracks review suggestion telemetry with parsed slash command", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const track = spyOn(Telemetry, "trackSuggestionShown") @@ -136,7 +136,7 @@ describe("suggestion", () => { test("show and accept parse local review arguments as local-review", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const shown = spyOn(Telemetry, "trackSuggestionShown") @@ -180,7 +180,7 @@ describe("suggestion", () => { test("non-review commands do not track suggestion telemetry", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const shown = spyOn(Telemetry, "trackSuggestionShown") @@ -203,7 +203,7 @@ describe("suggestion", () => { test("dismiss does not track accepted suggestion telemetry", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const shown = spyOn(Telemetry, "trackSuggestionShown") @@ -226,7 +226,7 @@ describe("suggestion", () => { test("invalid action index does not track accepted suggestion telemetry", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const shown = spyOn(Telemetry, "trackSuggestionShown") @@ -249,7 +249,7 @@ describe("suggestion", () => { test("dismiss rejects pending request and removes it", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const ask = Suggestion.show({ @@ -269,7 +269,7 @@ describe("suggestion", () => { test("dismissAll clears all pending suggestions for the target session", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { // Two suggestions for session A @@ -326,7 +326,7 @@ describe("suggestion", () => { test("dismissAll is a no-op when no suggestions exist", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { // Should not throw diff --git a/packages/opencode/test/kilocode/sync-event-encoding.test.ts b/packages/opencode/test/kilocode/sync-event-encoding.test.ts new file mode 100644 index 0000000000..130cf6548e --- /dev/null +++ b/packages/opencode/test/kilocode/sync-event-encoding.test.ts @@ -0,0 +1,110 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { SessionEvent } from "@opencode-ai/core/session-event" +import { DateTime, Effect, Layer, Schema } from "effect" +import { Bus } from "../../src/bus" +import { RuntimeFlags } from "../../src/effect/runtime-flags" +import { EventV2Bridge } from "../../src/event-v2-bridge" +import * as EventWire from "../../src/kilocode/event-wire" +import { SessionID } from "../../src/session/schema" +import { Database, eq } from "../../src/storage/db" +import { SyncEvent } from "../../src/sync" +import { EventTable } from "../../src/sync/event.sql" +import { resetDatabase } from "../fixture/db" +import { provideTmpdirInstance } from "../fixture/fixture" +import { awaitWithTimeout, testEffect } from "../lib/effect" + +const it = testEffect( + Layer.mergeAll( + SyncEvent.layer.pipe( + Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: true })), + Layer.provideMerge(Bus.layer), + ), + CrossSpawnSpawner.defaultLayer, + ), +) + +afterEach(resetDatabase) + +describe("SyncEvent encoding", () => { + test("preserves JSON values nested under unknown schemas", () => { + const schema = Schema.Struct({ value: Schema.Unknown }) + + expect(EventWire.encode(schema, { value: new Date(0) })).toEqual({ value: "1970-01-01T00:00:00.000Z" }) + expect(EventWire.encode(schema, { value: new URL("https://kilo.ai/docs") })).toEqual({ + value: "https://kilo.ai/docs", + }) + }) + + test("legacy timestamp decoding leaves unknown payload fields unchanged", () => { + const schema = Schema.Struct({ timestamp: Schema.DateTimeUtcFromMillis, input: Schema.Unknown }) + const timestamp = "1970-01-01T00:00:01.234Z" + const decoded = EventWire.decode(schema, { timestamp, input: { created: timestamp, released: timestamp } }) + + expect(DateTime.toEpochMillis(decoded.timestamp)).toBe(1_234) + expect(decoded.input).toEqual({ created: timestamp, released: timestamp }) + }) + + it.live( + "publishes encoded session data on the legacy bus", + provideTmpdirInstance(() => + Effect.gen(function* () { + const bus = yield* Bus.Service + const sync = yield* SyncEvent.Service + const def = EventV2Bridge.toSyncDefinition(SessionEvent.Text.Delta) + const sessionID = SessionID.make("ses_event_bus") + const events = new Array<{ type: string; properties: unknown }>() + const received = Promise.withResolvers() + const dispose = yield* bus.subscribeAllCallback((event) => { + if (event.type !== def.type) return + events.push(event) + received.resolve() + }) + + try { + yield* sync.run(def, { sessionID, timestamp: DateTime.makeUnsafe(1_234), delta: "hello" }) + yield* awaitWithTimeout( + Effect.promise(() => received.promise), + "legacy bus did not receive the session event", + ) + expect((events[0]?.properties as { timestamp?: unknown }).timestamp).toBe(1_234) + } finally { + dispose() + } + }), + ), + ) + + it.live( + "persists encoded session data and decodes it during replay", + provideTmpdirInstance(() => + Effect.gen(function* () { + const sync = yield* SyncEvent.Service + const def = EventV2Bridge.toSyncDefinition(SessionEvent.Text.Delta) + const sessionID = SessionID.make("ses_event_replay") + const timestamp = DateTime.makeUnsafe(1_234) + + yield* sync.run(def, { sessionID, timestamp, delta: "hello" }, { publish: false }) + const row = Database.use((db) => + db.select().from(EventTable).where(eq(EventTable.aggregate_id, sessionID)).get(), + ) + if (!row) throw new Error("missing persisted event") + expect((row.data as { timestamp?: unknown }).timestamp).toBe(1_234) + + yield* sync.remove(sessionID) + yield* sync.replay({ + id: row.id, + type: row.type, + seq: row.seq, + aggregateID: row.aggregate_id, + data: { ...row.data, timestamp: "1970-01-01T00:00:01.234Z" }, + }) + + const replayed = Database.use((db) => + db.select().from(EventTable).where(eq(EventTable.aggregate_id, sessionID)).get(), + ) + expect((replayed?.data as { timestamp?: unknown }).timestamp).toBe(1_234) + }), + ), + ) +}) diff --git a/packages/opencode/test/kilocode/tool-encoding.test.ts b/packages/opencode/test/kilocode/tool-encoding.test.ts index 5202a6d626..9ef84aac09 100644 --- a/packages/opencode/test/kilocode/tool-encoding.test.ts +++ b/packages/opencode/test/kilocode/tool-encoding.test.ts @@ -15,7 +15,7 @@ import { Bus } from "../../src/bus" import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" import { EditTool } from "../../src/tool/edit" import { Format } from "../../src/format" -import { Instance } from "../../src/project/instance" +import { Instance } from "../../src/kilocode/instance" import { Instruction } from "../../src/session/instruction" import { LSP } from "../../src/lsp/lsp" import { MessageID, SessionID } from "../../src/session/schema" diff --git a/packages/opencode/test/kilocode/tool-registry-indexing.test.ts b/packages/opencode/test/kilocode/tool-registry-indexing.test.ts index 781e4452e4..6a175505ce 100644 --- a/packages/opencode/test/kilocode/tool-registry-indexing.test.ts +++ b/packages/opencode/test/kilocode/tool-registry-indexing.test.ts @@ -9,7 +9,7 @@ import { KiloToolRegistry } from "../../src/kilocode/tool/registry" import { ModelID, ProviderID } from "../../src/provider/schema" import { ToolRegistry } from "../../src/tool/registry" import type * as Tool from "../../src/tool/tool" -import { Instance } from "../../src/project/instance" +import { Instance } from "../../src/kilocode/instance" import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture" import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/kilocode/tool-task-model.test.ts b/packages/opencode/test/kilocode/tool-task-model.test.ts index 3ec6979e63..66fc843c3b 100644 --- a/packages/opencode/test/kilocode/tool-task-model.test.ts +++ b/packages/opencode/test/kilocode/tool-task-model.test.ts @@ -11,7 +11,7 @@ import { Config } from "../../src/config/config" import { RuntimeFlags } from "../../src/effect/runtime-flags" import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" import { Global } from "@opencode-ai/core/global" -import { Instance } from "../../src/project/instance" +import { Instance } from "../../src/kilocode/instance" import { Session } from "../../src/session/session" import { MessageV2 } from "../../src/session/message-v2" import type { SessionPrompt } from "../../src/session/prompt" diff --git a/packages/opencode/test/lsp/client.test.ts b/packages/opencode/test/lsp/client.test.ts index 7d9f5a7155..93844d942f 100644 --- a/packages/opencode/test/lsp/client.test.ts +++ b/packages/opencode/test/lsp/client.test.ts @@ -1,11 +1,9 @@ import { beforeEach, describe, expect, test } from "bun:test" import path from "path" import { pathToFileURL } from "url" -import { tmpdir } from "../fixture/fixture" +import { tmpdir, withTestInstance } from "../fixture/fixture" import { LSPClient } from "@/lsp/client" import * as LSPServer from "@/lsp/server" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import * as Log from "@opencode-ai/core/util/log" function spawnFakeServer() { @@ -26,14 +24,15 @@ describe("LSPClient interop", () => { test("handles workspace/workspaceFolders request", async () => { const handle = spawnFakeServer() as any - const client = await WithInstance.provide({ + const client = await withTestInstance({ directory: process.cwd(), - fn: () => + fn: (ctx) => LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: process.cwd(), directory: process.cwd(), + instance: ctx, }), }) @@ -49,14 +48,15 @@ describe("LSPClient interop", () => { test("handles client/registerCapability request", async () => { const handle = spawnFakeServer() as any - const client = await WithInstance.provide({ + const client = await withTestInstance({ directory: process.cwd(), - fn: () => + fn: (ctx) => LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: process.cwd(), directory: process.cwd(), + instance: ctx, }), }) @@ -72,14 +72,15 @@ describe("LSPClient interop", () => { test("handles client/unregisterCapability request", async () => { const handle = spawnFakeServer() as any - const client = await WithInstance.provide({ + const client = await withTestInstance({ directory: process.cwd(), - fn: () => + fn: (ctx) => LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: process.cwd(), directory: process.cwd(), + instance: ctx, }), }) @@ -95,14 +96,15 @@ describe("LSPClient interop", () => { test("initialize does not overclaim unsupported diagnostics capabilities", async () => { const handle = spawnFakeServer() as any - const client = await WithInstance.provide({ + const client = await withTestInstance({ directory: process.cwd(), - fn: () => + fn: (ctx) => LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: process.cwd(), directory: process.cwd(), + instance: ctx, }), }) @@ -122,9 +124,9 @@ describe("LSPClient interop", () => { gamma: true, } - const client = await WithInstance.provide({ + const client = await withTestInstance({ directory: process.cwd(), - fn: () => + fn: (ctx) => LSPClient.create({ serverID: "fake", server: { @@ -133,6 +135,7 @@ describe("LSPClient interop", () => { }, root: process.cwd(), directory: process.cwd(), + instance: ctx, }), }) @@ -151,14 +154,15 @@ describe("LSPClient interop", () => { const file = path.join(tmp.path, "client.ts") await Bun.write(file, "first\n") - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { const client = await LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: tmp.path, directory: tmp.path, + instance: ctx, }) await client.notify.open({ path: file }) @@ -194,14 +198,15 @@ describe("LSPClient interop", () => { const file = path.join(tmp.path, "client.ts") await Bun.write(file, "const x = 1\n") - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { const client = await LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: tmp.path, directory: tmp.path, + instance: ctx, }) const version = await client.notify.open({ path: file }) @@ -240,14 +245,15 @@ describe("LSPClient interop", () => { const file = path.join(tmp.path, "client.ts") await Bun.write(file, "const x = 1\n") - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { const client = await LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: tmp.path, directory: tmp.path, + instance: ctx, }) const version = await client.notify.open({ path: file }) @@ -287,14 +293,15 @@ describe("LSPClient interop", () => { const file = path.join(tmp.path, "client.cs") await Bun.write(file, "class C {}\n") - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { const client = await LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: tmp.path, directory: tmp.path, + instance: ctx, }) await client.connection.sendRequest("test/configure-pull-diagnostics", { @@ -335,14 +342,15 @@ describe("LSPClient interop", () => { const file = path.join(tmp.path, "client.cs") await Bun.write(file, "class C {}\n") - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { const client = await LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: tmp.path, directory: tmp.path, + instance: ctx, }) await client.connection.sendRequest("test/configure-pull-diagnostics", { @@ -388,14 +396,15 @@ describe("LSPClient interop", () => { await Bun.write(file, "class C {}\n") await Bun.write(related, "class D {}\n") - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { const client = await LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: tmp.path, directory: tmp.path, + instance: ctx, }) await client.connection.sendRequest("test/configure-pull-diagnostics", { @@ -452,14 +461,15 @@ describe("LSPClient interop", () => { const file = path.join(tmp.path, "client.cs") await Bun.write(file, "class C {}\n") - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { const client = await LSPClient.create({ serverID: "fake", server: handle as unknown as LSPServer.Handle, root: tmp.path, directory: tmp.path, + instance: ctx, }) await client.connection.sendRequest("test/configure-pull-diagnostics", { diff --git a/packages/opencode/test/lsp/index.test.ts b/packages/opencode/test/lsp/index.test.ts index 8520afae21..be76ab43d3 100644 --- a/packages/opencode/test/lsp/index.test.ts +++ b/packages/opencode/test/lsp/index.test.ts @@ -1,17 +1,17 @@ import { describe, expect, spyOn, test } from "bun:test" import path from "path" import fs from "fs/promises" -import { Effect, Layer } from "effect" +import { Deferred, Effect, Layer } from "effect" +import { Bus } from "@/bus" import { Config } from "@/config/config" import { RuntimeFlags } from "@/effect/runtime-flags" import { LSP } from "@/lsp/lsp" import * as LSPServer from "@/lsp/server" import * as launch from "../../src/lsp/launch" // kilocode_change - spy on spawn import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { provideTmpdirInstance, tmpdir } from "../fixture/fixture" -import { testEffect } from "../lib/effect" -import { type InstanceContext } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { provideTestInstance, provideTmpdirInstance, tmpdir } from "../fixture/fixture" +import { awaitWithTimeout, testEffect } from "../lib/effect" +import { type InstanceContext } from "../../src/project/instance-context" import { Flag } from "@opencode-ai/core/flag/flag" // kilocode_change import { TsCheck } from "../../src/kilocode/ts-check" // kilocode_change @@ -26,6 +26,13 @@ const experimentalTyIt = testEffect( CrossSpawnSpawner.defaultLayer, ), ) +const fakeServerPath = path.join(__dirname, "../fixture/lsp/fake-lsp-server.js") +const disabledDownloadIt = testEffect( + Layer.mergeAll( + LSP.layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(RuntimeFlags.layer({ disableLspDownload: true }))), + CrossSpawnSpawner.defaultLayer, + ), +) describe("lsp.spawn", () => { it.live("does not spawn builtin LSP for files outside instance", () => @@ -107,6 +114,35 @@ describe("lsp.spawn", () => { ), ) + it.live("publishes lsp.updated after custom LSP initialization", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const lsp = yield* LSP.Service + const updated = yield* Deferred.make() + const unsubscribe = Bus.subscribe(LSP.Event.Updated, () => + Effect.runSync(Deferred.succeed(updated, undefined)), + ) + yield* Effect.addFinalizer(() => Effect.sync(unsubscribe)) + + const file = path.join(dir, "sample.repro") + yield* Effect.promise(() => Bun.write(file, "sample\n")) + yield* lsp.touchFile(file) + yield* awaitWithTimeout(Deferred.await(updated), "lsp.updated event was not published") + }), + { + config: { + lsp: { + fake: { + command: [process.execPath, fakeServerPath], + extensions: [".repro"], + }, + }, + }, + }, + ), + ) + experimentalToolIt.live("would spawn builtin LSP for files inside instance when config object is provided", () => provideTmpdirInstance( (dir) => @@ -149,7 +185,7 @@ describe("lsp.spawn", () => { const tsgoSpy = spyOn(TsCheck, "native_tsgo").mockResolvedValue("/fake/tsgo") try { - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const result = await LSPServer.Typescript.spawn(tmp.path, fakeCtx, fakeFlags) @@ -230,4 +266,28 @@ describe("lsp.spawn", () => { { config: { lsp: true } }, ), ) + + disabledDownloadIt.live("passes disableLspDownload to builtin LSP spawn", () => + provideTmpdirInstance( + (dir) => + LSP.Service.use((lsp) => + Effect.gen(function* () { + const pyright = spyOn(LSPServer.Pyright, "spawn").mockResolvedValue(undefined) + + try { + yield* lsp.hover({ + file: path.join(dir, "src", "inside.py"), + line: 0, + character: 0, + }) + expect(pyright).toHaveBeenCalledTimes(1) + expect(pyright.mock.calls[0]?.[2]).toMatchObject({ disableLspDownload: true }) + } finally { + pyright.mockRestore() + } + }), + ), + { config: { lsp: true } }, + ), + ) }) diff --git a/packages/opencode/test/plugin/workspace-adapter.test.ts b/packages/opencode/test/plugin/workspace-adapter.test.ts index 41dbf53445..52eb9d4f3c 100644 --- a/packages/opencode/test/plugin/workspace-adapter.test.ts +++ b/packages/opencode/test/plugin/workspace-adapter.test.ts @@ -15,10 +15,11 @@ import { RuntimeFlags } from "../../src/effect/runtime-flags" import { Workspace } from "../../src/control-plane/workspace" import { Plugin } from "../../src/plugin/index" import { InstanceBootstrap } from "../../src/project/bootstrap-service" -import { Instance } from "../../src/project/instance" +import { Instance } from "../../src/kilocode/instance" import { InstanceStore } from "../../src/project/instance-store" import { Project } from "../../src/project/project" import { Vcs } from "../../src/project/vcs" +import { InstanceState } from "../../src/effect/instance-state" import { Session } from "../../src/session/session" import { SessionPrompt } from "../../src/session/prompt" import { SyncEvent } from "../../src/sync" @@ -116,11 +117,12 @@ describe("plugin.workspace", () => { const plugin = yield* Plugin.Service yield* plugin.init() const workspace = yield* Workspace.Service + const ctx = yield* InstanceState.context const info = yield* workspace.create({ type, branch: null, extra: { key: "value" }, - projectID: Instance.project.id, + projectID: ctx.project.id, }) expect(info.type).toBe(type) diff --git a/packages/opencode/test/project/instance-bootstrap.test.ts b/packages/opencode/test/project/instance-bootstrap.test.ts index 4be2a76113..c5b18cc5b8 100644 --- a/packages/opencode/test/project/instance-bootstrap.test.ts +++ b/packages/opencode/test/project/instance-bootstrap.test.ts @@ -3,12 +3,13 @@ import { existsSync } from "node:fs" import path from "node:path" import { pathToFileURL } from "node:url" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { Effect, Layer } from "effect" +import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap" import { InstanceLayer } from "../../src/project/instance-layer" import { InstanceStore } from "../../src/project/instance-store" import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" +import { waitGlobalBusEvent } from "../server/global-bus" const it = testEffect(Layer.mergeAll(InstanceLayer.layer, CrossSpawnSpawner.defaultLayer)) @@ -54,6 +55,13 @@ const bootstrapFixture = Effect.gen(function* () { return { directory: dir, marker } }) +function waitDisposed(directory: string) { + return waitGlobalBusEvent({ + message: "timed out waiting for CLI bootstrap instance disposal", + predicate: (event) => event.payload.type === "server.instance.disposed" && event.directory === directory, + }) +} + it.live("InstanceStore.provide runs InstanceBootstrap before effect", () => Effect.gen(function* () { const tmp = yield* bootstrapFixture @@ -75,6 +83,21 @@ it.live("CLI bootstrap runs InstanceBootstrap before callback", () => }), ) +it.live("CLI bootstrap disposes the instance when the callback rejects", () => + Effect.gen(function* () { + const tmp = yield* bootstrapFixture + const disposed = yield* waitDisposed(tmp.directory).pipe(Effect.forkScoped) + + const exit = yield* Effect.promise(() => + cliBootstrap(tmp.directory, async () => Promise.reject(new Error("boom"))), + ).pipe(Effect.exit) + + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toMatchObject({ message: "boom" }) + yield* Fiber.join(disposed) + }), +) + it.live("InstanceStore.reload runs InstanceBootstrap", () => Effect.gen(function* () { const tmp = yield* bootstrapFixture diff --git a/packages/opencode/test/project/instance.test.ts b/packages/opencode/test/project/instance.test.ts index 5d5337d584..383ad0208f 100644 --- a/packages/opencode/test/project/instance.test.ts +++ b/packages/opencode/test/project/instance.test.ts @@ -4,9 +4,9 @@ import { Deferred, Effect, Fiber, Layer } from "effect" import { InstanceRef } from "../../src/effect/instance-ref" import { registerDisposer } from "../../src/effect/instance-registry" import { InstanceBootstrap } from "../../src/project/bootstrap-service" -import { Instance } from "../../src/project/instance" import { InstanceStore } from "../../src/project/instance-store" -import { TestInstance, tmpdirScoped } from "../fixture/fixture" +import { capture } from "../../src/kilocode/instance" // kilocode_change +import { tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" let bootstrapRun: Effect.Effect = Effect.void @@ -37,7 +37,7 @@ const registerDisposerScoped = (disposer: (directory: string) => Promise) ) describe("InstanceStore", () => { - it.live("loads instance context without installing ALS for the caller", () => + it.live("loads instance context", () => Effect.gen(function* () { const dir = yield* tmpdirScoped({ git: true }) const store = yield* InstanceStore.Service @@ -45,7 +45,11 @@ describe("InstanceStore", () => { expect(ctx.directory).toBe(dir) expect(ctx.worktree).toBe(dir) - expect(() => Instance.current).toThrow() + + // kilocode_change start - capture prefers legacy ALS, then falls back to the Effect fiber reference + const fallback = yield* Effect.sync(capture).pipe(Effect.provideService(InstanceRef, ctx)) + expect({ ambient: capture(), fallback }).toEqual({ ambient: undefined, fallback: ctx }) + // kilocode_change end }), ) @@ -63,7 +67,7 @@ describe("InstanceStore", () => { yield* store.load({ directory: dir }) expect(initializedDirectory).toBe(dir) - expect(() => Instance.current).toThrow() + expect(capture()).toBeUndefined() // kilocode_change - bootstrap legacy ALS does not leak into the caller }), ) @@ -166,6 +170,26 @@ describe("InstanceStore", () => { }), ) + // kilocode_change start - reload disposers retain legacy instance context + it.live("runs reload disposers under the previous instance context", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped({ git: true }) + const store = yield* InstanceStore.Service + const first = yield* store.load({ directory: dir }) + let captured: ReturnType + yield* registerDisposerScoped(async (directory) => { + if (directory !== dir) return + await Promise.resolve() + captured = capture() + }) + + yield* store.reload({ directory: dir }) + + expect(captured).toBe(first) + }), + ) + // kilocode_change end + it.live("stale dispose does not delete an in-flight reload", () => Effect.gen(function* () { const dir = yield* tmpdirScoped({ git: true }) @@ -246,34 +270,17 @@ describe("InstanceStore", () => { }), ) - it.instance( - "provides legacy Promise callers with instance ALS", - () => - Effect.gen(function* () { - const test = yield* TestInstance - const ctx = yield* InstanceRef - if (!ctx) throw new Error("InstanceRef not provided") - - const directory = yield* Effect.promise(() => Promise.resolve(Instance.restore(ctx, () => Instance.directory))) - - expect(directory).toBe(test.directory) - expect(() => Instance.current).toThrow() - }), - { git: true }, - ) - - // kilocode_change - InstanceStore.boot wraps bootstrap.run in the Instance ALS so + // kilocode_change - InstanceStore.boot provides InstanceRef to bootstrap.run so // KilocodeBootstrap (and anything it forkDetaches, e.g. KiloIndexing.init) can read - // Instance.directory. Upstream runs bootstrap without the ALS; this regression test - // pins the Kilo contract. - it.live("installs Instance ALS around bootstrap for Kilo bootstrap compatibility", () => + // the current directory. This regression test pins the Kilo contract. + it.live("provides InstanceRef during bootstrap for Kilo bootstrap compatibility", () => Effect.gen(function* () { const dir = yield* tmpdirScoped({ git: true }) const store = yield* InstanceStore.Service let directoryDuringBootstrap: string | undefined - bootstrapRun = Effect.sync(() => { - directoryDuringBootstrap = Instance.directory + bootstrapRun = Effect.gen(function* () { + directoryDuringBootstrap = (yield* InstanceRef)?.directory }) yield* store.load({ directory: dir }) diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index 308e2f957b..688b818bee 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -5,7 +5,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect" import { GlobalBus, type GlobalEvent } from "../../src/bus/global" import { Git } from "../../src/git" -import { Instance } from "../../src/project/instance" +import { InstanceRef } from "../../src/effect/instance-ref" import { InstanceRuntime } from "../../src/project/instance-runtime" import { Worktree } from "../../src/worktree" import { disposeAllInstances, provideInstance, TestInstance } from "../fixture/fixture" @@ -41,7 +41,10 @@ const waitReady = Effect.fn("WorktreeTest.waitReady")(function* () { const removeCreatedWorktree = (directory: string) => Effect.gen(function* () { const svc = yield* Worktree.Service - const ctx = yield* Effect.sync(() => Instance.current).pipe(provideInstance(directory)) + const ctx = yield* Effect.gen(function* () { + return yield* InstanceRef + }).pipe(provideInstance(directory)) + if (!ctx) return yield* Effect.die(new Error("missing test instance")) yield* Effect.promise(() => InstanceRuntime.disposeInstance(ctx)) const ok = yield* svc.remove({ directory }) if (!ok) return yield* Effect.fail(new Error(`failed to remove worktree ${directory}`)) diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts index c75231d44d..82f1a86a99 100644 --- a/packages/opencode/test/provider/amazon-bedrock.test.ts +++ b/packages/opencode/test/provider/amazon-bedrock.test.ts @@ -1,28 +1,47 @@ -import { test, expect, describe } from "bun:test" +import { afterEach, test, expect, describe } from "bun:test" import path from "path" import { unlink } from "fs/promises" import { ProviderID } from "../../src/provider/schema" -import { tmpdir } from "../fixture/fixture" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { disposeAllInstances, tmpdir, withTestInstance } from "../fixture/fixture" +import type { InstanceContext } from "../../src/project/instance-context" import { Provider } from "@/provider/provider" import { Env } from "../../src/env" import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util/filesystem" import { Effect } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" +import { InstanceRef } from "../../src/effect/instance-ref" import { makeRuntime } from "../../src/effect/run-service" const env = makeRuntime(Env.Service, Env.defaultLayer) -const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v)) +const originalEnv = new Map() -async function list() { +function rememberEnv(k: string) { + if (!originalEnv.has(k)) originalEnv.set(k, process.env[k]) +} + +const set = (ctx: InstanceContext, k: string, v: string) => { + rememberEnv(k) + process.env[k] = v + return env.runSync((svc) => svc.set(k, v).pipe(Effect.provideService(InstanceRef, ctx))) +} + +afterEach(async () => { + for (const [key, value] of originalEnv) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + originalEnv.clear() + await disposeAllInstances() +}) + +async function list(ctx: InstanceContext) { return AppRuntime.runPromise( Effect.gen(function* () { const provider = yield* Provider.Service return yield* provider.list() - }), + }).pipe(Effect.provideService(InstanceRef, ctx)), ) } @@ -44,12 +63,12 @@ test("Bedrock: config region takes precedence over AWS_REGION env var", async () ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_REGION", "us-east-1") - set("AWS_PROFILE", "default") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_REGION", "us-east-1") + set(ctx, "AWS_PROFILE", "default") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1") }, @@ -67,12 +86,12 @@ test("Bedrock: falls back to AWS_REGION env var when no config region", async () ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_REGION", "eu-west-1") - set("AWS_PROFILE", "default") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_REGION", "eu-west-1") + set(ctx, "AWS_PROFILE", "default") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1") }, @@ -120,13 +139,13 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => { }), ) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_PROFILE", "") - set("AWS_ACCESS_KEY_ID", "") - set("AWS_BEARER_TOKEN_BEDROCK", "") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_PROFILE", "") + set(ctx, "AWS_ACCESS_KEY_ID", "") + set(ctx, "AWS_BEARER_TOKEN_BEDROCK", "") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1") }, @@ -164,12 +183,12 @@ test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_PROFILE", "default") - set("AWS_ACCESS_KEY_ID", "test-key-id") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_PROFILE", "default") + set(ctx, "AWS_ACCESS_KEY_ID", "test-key-id") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock].options?.region).toBe("us-east-1") }, @@ -194,11 +213,11 @@ test("Bedrock: includes custom endpoint in options when specified", async () => ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_PROFILE", "default") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_PROFILE", "default") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock].options?.endpoint).toBe( "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com", @@ -225,14 +244,14 @@ test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async () ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token") - set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role") - set("AWS_PROFILE", "") - set("AWS_ACCESS_KEY_ID", "") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token") + set(ctx, "AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role") + set(ctx, "AWS_PROFILE", "") + set(ctx, "AWS_ACCESS_KEY_ID", "") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock].options?.region).toBe("us-east-1") }, @@ -266,11 +285,11 @@ test("Bedrock: model with us. prefix should not be double-prefixed", async () => ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_PROFILE", "default") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_PROFILE", "default") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() // The model should exist with the us. prefix expect(providers[ProviderID.amazonBedrock].models["us.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined() @@ -301,11 +320,11 @@ test("Bedrock: model with global. prefix should not be prefixed", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_PROFILE", "default") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_PROFILE", "default") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock].models["global.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined() }, @@ -335,11 +354,11 @@ test("Bedrock: model with eu. prefix should not be double-prefixed", async () => ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_PROFILE", "default") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_PROFILE", "default") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock].models["eu.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined() }, @@ -369,11 +388,11 @@ test("Bedrock: model without prefix in US region should get us. prefix added", a ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("AWS_PROFILE", "default") - const providers = await list() + fn: async (ctx) => { + set(ctx, "AWS_PROFILE", "default") + const providers = await list(ctx) expect(providers[ProviderID.amazonBedrock]).toBeDefined() // Non-prefixed model should still be registered expect(providers[ProviderID.amazonBedrock].models["anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined() diff --git a/packages/opencode/test/provider/gitlab-duo.test.ts b/packages/opencode/test/provider/gitlab-duo.test.ts index 8bb3b96347..4ac62cf69d 100644 --- a/packages/opencode/test/provider/gitlab-duo.test.ts +++ b/packages/opencode/test/provider/gitlab-duo.test.ts @@ -7,9 +7,7 @@ export {} // import path from "path" // import { ProviderID, ModelID } from "../../src/provider/schema" -// import { tmpdir } from "../fixture/fixture" -// import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +// import { tmpdir, withTestInstance } from "../fixture/fixture" // import { Provider } from "@/provider/provider" // import { Env } from "../../src/env" // import { Global } from "@opencode-ai/core/global" @@ -26,7 +24,7 @@ import { WithInstance } from "../../src/project/with-instance" // ) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-gitlab-token") @@ -57,7 +55,7 @@ import { WithInstance } from "../../src/project/with-instance" // ) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-token") @@ -96,7 +94,7 @@ import { WithInstance } from "../../src/project/with-instance" // }), // ) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "") @@ -131,7 +129,7 @@ import { WithInstance } from "../../src/project/with-instance" // }), // ) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "") @@ -163,7 +161,7 @@ import { WithInstance } from "../../src/project/with-instance" // ) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_INSTANCE_URL", "https://gitlab.company.internal") @@ -194,7 +192,7 @@ import { WithInstance } from "../../src/project/with-instance" // ) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "env-token") @@ -217,7 +215,7 @@ import { WithInstance } from "../../src/project/with-instance" // ) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-token") @@ -253,7 +251,7 @@ import { WithInstance } from "../../src/project/with-instance" // ) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-token") @@ -278,7 +276,7 @@ import { WithInstance } from "../../src/project/with-instance" // ) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-token") @@ -302,7 +300,7 @@ import { WithInstance } from "../../src/project/with-instance" // await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-token") @@ -350,7 +348,7 @@ import { WithInstance } from "../../src/project/with-instance" // await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-token") @@ -373,7 +371,7 @@ import { WithInstance } from "../../src/project/with-instance" // await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-token") @@ -397,7 +395,7 @@ import { WithInstance } from "../../src/project/with-instance" // await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) // }, // }) -// await WithInstance.provide({ +// await withTestInstance({ // directory: tmp.path, // init: async () => { // Env.set("GITLAB_TOKEN", "test-token") diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 7517318c07..267177e44c 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1,12 +1,10 @@ -import { test, expect } from "bun:test" +import { afterEach, test, expect } from "bun:test" import { mkdir, unlink } from "fs/promises" import path from "path" -import { tmpdir } from "../fixture/fixture" +import { disposeAllInstances, tmpdir, withTestInstance } from "../fixture/fixture" import { Global } from "@opencode-ai/core/global" -import { Instance } from "../../src/project/instance" -import { InstanceRuntime } from "../../src/project/instance-runtime" -import { WithInstance } from "../../src/project/with-instance" +import type { InstanceContext } from "../../src/project/instance-context" import { Plugin } from "../../src/plugin/index" import { ModelsDev } from "@opencode-ai/core/models" import { Provider } from "@/provider/provider" @@ -15,6 +13,7 @@ import { Filesystem } from "@/util/filesystem" import { Env } from "../../src/env" import { Effect, Layer } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" +import { InstanceRef } from "../../src/effect/instance-ref" import { makeRuntime } from "../../src/effect/run-service" import { testEffect } from "../lib/effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -23,8 +22,31 @@ import { Auth } from "@/auth" import { RuntimeFlags } from "@/effect/runtime-flags" const env = makeRuntime(Env.Service, Env.defaultLayer) -const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v)) -const remove = (k: string) => env.runSync((svc) => svc.remove(k)) +const originalEnv = new Map() + +function rememberEnv(k: string) { + if (!originalEnv.has(k)) originalEnv.set(k, process.env[k]) +} + +const set = (ctx: InstanceContext, k: string, v: string) => { + rememberEnv(k) + process.env[k] = v + return env.runSync((svc) => svc.set(k, v).pipe(Effect.provideService(InstanceRef, ctx))) +} +const remove = (ctx: InstanceContext, k: string) => { + rememberEnv(k) + delete process.env[k] + return env.runSync((svc) => svc.remove(k).pipe(Effect.provideService(InstanceRef, ctx))) +} + +afterEach(async () => { + for (const [key, value] of originalEnv) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + originalEnv.clear() + await disposeAllInstances() +}) const providerLayer = (flags: Partial = {}) => Provider.layer.pipe( @@ -37,54 +59,54 @@ const providerLayer = (flags: Partial = {}) => Layer.provide(RuntimeFlags.layer(flags)), ) -async function run(fn: (provider: Provider.Interface) => Effect.Effect) { +async function run(ctx: InstanceContext, fn: (provider: Provider.Interface) => Effect.Effect) { return AppRuntime.runPromise( Effect.gen(function* () { const provider = yield* Provider.Service return yield* fn(provider) - }), + }).pipe(Effect.provideService(InstanceRef, ctx)), ) } -async function list() { - return run((provider) => provider.list()) +async function list(ctx: InstanceContext) { + return run(ctx, (provider) => provider.list()) } -async function getProvider(providerID: ProviderID) { - return run((provider) => provider.getProvider(providerID)) +async function getProvider(providerID: ProviderID, ctx: InstanceContext) { + return run(ctx, (provider) => provider.getProvider(providerID)) } -async function getModel(providerID: ProviderID, modelID: ModelID) { - return run((provider) => provider.getModel(providerID, modelID)) +async function getModel(providerID: ProviderID, modelID: ModelID, ctx: InstanceContext) { + return run(ctx, (provider) => provider.getModel(providerID, modelID)) } -async function getLanguage(model: Provider.Model) { - return run((provider) => provider.getLanguage(model)) +async function getLanguage(model: Provider.Model, ctx: InstanceContext) { + return run(ctx, (provider) => provider.getLanguage(model)) } -async function closest(providerID: ProviderID, query: string[]) { - return run((provider) => provider.closest(providerID, query)) +async function closest(providerID: ProviderID, query: string[], ctx: InstanceContext) { + return run(ctx, (provider) => provider.closest(providerID, query)) } -async function getSmallModel(providerID: ProviderID) { - return run((provider) => provider.getSmallModel(providerID)) +async function getSmallModel(providerID: ProviderID, ctx: InstanceContext) { + return run(ctx, (provider) => provider.getSmallModel(providerID)) } -async function defaultModel() { - return run((provider) => provider.defaultModel()) +async function defaultModel(ctx: InstanceContext) { + return run(ctx, (provider) => provider.defaultModel()) } async function markPluginDependenciesReady(dir: string) { await mkdir(path.join(dir, "node_modules"), { recursive: true }) await Bun.write( path.join(dir, "package-lock.json"), - JSON.stringify({ packages: { "": { dependencies: { "@kilocode/plugin": "0.0.0" } } } }), // kilocode_change + JSON.stringify({ packages: { "": { dependencies: { "@kilocode/plugin": "0.0.0" } } } }), ) } function paid(providers: Awaited>) { const item = providers[ProviderID.make("opencode")] - if (!item) return 0 + if (!item) return 0 // kilocode_change - Kilo drops opencode provider without apiKey/auth return Object.values(item.models).filter((model) => model.cost.input > 0).length } @@ -124,11 +146,11 @@ test("provider loaded from env variable", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() // Provider should retain its connection source even if custom loaders // merge additional options. @@ -156,10 +178,10 @@ test("provider loaded from config with apiKey option", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() }, }) @@ -177,11 +199,11 @@ test("disabled_providers excludes provider", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeUndefined() }, }) @@ -199,12 +221,12 @@ test("enabled_providers restricts to only listed providers", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - set("OPENAI_API_KEY", "test-openai-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + set(ctx, "OPENAI_API_KEY", "test-openai-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() expect(providers[ProviderID.openai]).toBeUndefined() }, @@ -227,11 +249,11 @@ test("model whitelist filters models for provider", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() const models = Object.keys(providers[ProviderID.anthropic].models) expect(models).toContain("claude-sonnet-4-20250514") @@ -256,11 +278,11 @@ test("model blacklist excludes specific models", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() const models = Object.keys(providers[ProviderID.anthropic].models) expect(models).not.toContain("claude-sonnet-4-20250514") @@ -289,11 +311,11 @@ test("custom model alias via config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() expect(providers[ProviderID.anthropic].models["my-alias"]).toBeDefined() expect(providers[ProviderID.anthropic].models["my-alias"].name).toBe("My Custom Alias") @@ -333,10 +355,10 @@ test("custom provider with npm package", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("custom-provider")]).toBeDefined() expect(providers[ProviderID.make("custom-provider")].name).toBe("Custom Provider") expect(providers[ProviderID.make("custom-provider")].models["custom-model"]).toBeDefined() @@ -410,10 +432,10 @@ test("custom DeepSeek openai-compatible model defaults interleaved reasoning fie ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) const provider = providers[ProviderID.make("custom-provider")] expect(provider.models["deepseek-r1"].capabilities.interleaved).toEqual({ field: "reasoning_content" }) expect(provider.models["deepseek-details"].capabilities.interleaved).toEqual({ field: "reasoning_details" }) @@ -444,11 +466,11 @@ test("env variable takes precedence, config merges options", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "env-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "env-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() // Config options should be merged expect(providers[ProviderID.anthropic].options.timeout).toBe(60000) @@ -468,15 +490,15 @@ test("getModel returns model for valid provider/model", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514")) + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"), ctx) expect(model).toBeDefined() expect(String(model.providerID)).toBe("anthropic") expect(String(model.id)).toBe("claude-sonnet-4-20250514") - const language = await getLanguage(model) + const language = await getLanguage(model, ctx) expect(language).toBeDefined() }, }) @@ -493,11 +515,11 @@ test("getModel throws ModelNotFoundError for invalid model", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - expect(getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"))).rejects.toThrow() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + expect(getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"), ctx)).rejects.toThrow() }, }) }) @@ -513,10 +535,10 @@ test("getModel throws ModelNotFoundError for invalid provider", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - expect(getModel(ProviderID.make("nonexistent-provider"), ModelID.make("some-model"))).rejects.toThrow() + fn: async (ctx) => { + expect(getModel(ProviderID.make("nonexistent-provider"), ModelID.make("some-model"), ctx)).rejects.toThrow() }, }) }) @@ -544,11 +566,11 @@ test("defaultModel returns first available model when no config set", async () = ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const model = await defaultModel() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const model = await defaultModel(ctx) expect(model.providerID).toBeDefined() expect(model.modelID).toBeDefined() }, @@ -567,11 +589,11 @@ test("defaultModel respects config model setting", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const model = await defaultModel() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const model = await defaultModel(ctx) expect(String(model.providerID)).toBe("anthropic") expect(String(model.modelID)).toBe("claude-sonnet-4-20250514") }, @@ -700,11 +722,11 @@ test("closest finds model by partial match", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const result = await closest(ProviderID.anthropic, ["sonnet-4"]) + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const result = await closest(ProviderID.anthropic, ["sonnet-4"], ctx) expect(result).toBeDefined() expect(String(result?.providerID)).toBe("anthropic") expect(String(result?.modelID)).toContain("sonnet-4") @@ -723,10 +745,10 @@ test("closest returns undefined for nonexistent provider", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const result = await closest(ProviderID.make("nonexistent"), ["model"]) + fn: async (ctx) => { + const result = await closest(ProviderID.make("nonexistent"), ["model"], ctx) expect(result).toBeUndefined() }, }) @@ -753,14 +775,14 @@ test("getModel uses realIdByKey for aliased models", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic].models["my-sonnet"]).toBeDefined() - const model = await getModel(ProviderID.anthropic, ModelID.make("my-sonnet")) + const model = await getModel(ProviderID.anthropic, ModelID.make("my-sonnet"), ctx) expect(model).toBeDefined() expect(String(model.id)).toBe("my-sonnet") expect(model.name).toBe("My Sonnet Alias") @@ -797,10 +819,10 @@ test("provider api field sets model api.url", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) // api field is stored on model.api.url, used by getSDK to set baseURL expect(providers[ProviderID.make("custom-api")].models["model-1"].api.url).toBe("https://api.example.com/v1") }, @@ -837,10 +859,10 @@ test("explicit baseURL overrides api field", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("custom-api")].options.baseURL).toBe("https://custom.override.com/v1") }, }) @@ -866,11 +888,11 @@ test("model inherits properties from existing database model", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] expect(model.name).toBe("Custom Name for Sonnet") expect(model.capabilities.toolcall).toBe(true) @@ -892,11 +914,11 @@ test("disabled_providers prevents loading even with env var", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("OPENAI_API_KEY", "test-openai-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "OPENAI_API_KEY", "test-openai-key") + const providers = await list(ctx) expect(providers[ProviderID.openai]).toBeUndefined() }, }) @@ -914,12 +936,12 @@ test("enabled_providers with empty array allows no providers", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - set("OPENAI_API_KEY", "test-openai-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + set(ctx, "OPENAI_API_KEY", "test-openai-key") + const providers = await list(ctx) expect(Object.keys(providers).length).toBe(0) }, }) @@ -942,11 +964,11 @@ test("whitelist and blacklist can be combined", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() const models = Object.keys(providers[ProviderID.anthropic].models) expect(models).toContain("claude-sonnet-4-20250514") @@ -982,10 +1004,10 @@ test("model modalities default correctly", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) const model = providers[ProviderID.make("test-provider")].models["test-model"] expect(model.capabilities.input.text).toBe(true) expect(model.capabilities.output.text).toBe(true) @@ -1025,10 +1047,10 @@ test("model with custom cost values", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) const model = providers[ProviderID.make("test-provider")].models["test-model"] expect(model.cost.input).toBe(5) expect(model.cost.output).toBe(15) @@ -1049,11 +1071,11 @@ test("getSmallModel returns appropriate small model", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const model = await getSmallModel(ProviderID.anthropic) + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const model = await getSmallModel(ProviderID.anthropic, ctx) expect(model).toBeDefined() expect(model?.id).toContain("haiku") }, @@ -1072,11 +1094,11 @@ test("getSmallModel respects config small_model override", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const model = await getSmallModel(ProviderID.anthropic) + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const model = await getSmallModel(ProviderID.anthropic, ctx) expect(model).toBeDefined() expect(String(model?.providerID)).toBe("anthropic") expect(String(model?.id)).toBe("claude-sonnet-4-20250514") @@ -1096,11 +1118,11 @@ test("getSmallModel ignores invalid config small_model", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - expect(await getSmallModel(ProviderID.anthropic)).toBeUndefined() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + expect(await getSmallModel(ProviderID.anthropic, ctx)).toBeUndefined() }, }) }) @@ -1139,12 +1161,12 @@ test("multiple providers can be configured simultaneously", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-anthropic-key") - set("OPENAI_API_KEY", "test-openai-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-anthropic-key") + set(ctx, "OPENAI_API_KEY", "test-openai-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() expect(providers[ProviderID.openai]).toBeDefined() expect(providers[ProviderID.anthropic].options.timeout).toBe(30000) @@ -1182,10 +1204,10 @@ test("provider with custom npm package", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("local-llm")]).toBeDefined() expect(providers[ProviderID.make("local-llm")].models["llama-3"].api.npm).toBe("@ai-sdk/openai-compatible") expect(providers[ProviderID.make("local-llm")].options.baseURL).toBe("http://localhost:11434/v1") @@ -1216,11 +1238,11 @@ test("model alias name defaults to alias key when id differs", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic].models["sonnet"].name).toBe("sonnet") }, }) @@ -1254,11 +1276,11 @@ test("provider with multiple env var options only includes apiKey when single en ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("MULTI_ENV_KEY_1", "test-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "MULTI_ENV_KEY_1", "test-key") + const providers = await list(ctx) expect(providers[ProviderID.make("multi-env")]).toBeDefined() // When multiple env options exist, key should NOT be auto-set expect(providers[ProviderID.make("multi-env")].key).toBeUndefined() @@ -1294,11 +1316,11 @@ test("provider with single env var includes apiKey automatically", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("SINGLE_ENV_KEY", "my-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "SINGLE_ENV_KEY", "my-api-key") + const providers = await list(ctx) expect(providers[ProviderID.make("single-env")]).toBeDefined() // Single env option should auto-set key expect(providers[ProviderID.make("single-env")].key).toBe("my-api-key") @@ -1329,11 +1351,11 @@ test("model cost overrides existing cost values", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] expect(model.cost.input).toBe(999) expect(model.cost.output).toBe(888) @@ -1377,10 +1399,10 @@ test("completely new provider not in database can be configured", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("brand-new-provider")]).toBeDefined() expect(providers[ProviderID.make("brand-new-provider")].name).toBe("Brand New") const model = providers[ProviderID.make("brand-new-provider")].models["new-model"] @@ -1406,13 +1428,13 @@ test("disabled_providers and enabled_providers interaction", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-anthropic") - set("OPENAI_API_KEY", "test-openai") - set("GOOGLE_GENERATIVE_AI_API_KEY", "test-google") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-anthropic") + set(ctx, "OPENAI_API_KEY", "test-openai") + set(ctx, "GOOGLE_GENERATIVE_AI_API_KEY", "test-google") + const providers = await list(ctx) // anthropic: in enabled, not in disabled = allowed expect(providers[ProviderID.anthropic]).toBeDefined() // openai: in enabled, but also in disabled = NOT allowed @@ -1449,10 +1471,10 @@ test("model with tool_call false", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("no-tools")].models["basic-model"].capabilities.toolcall).toBe(false) }, }) @@ -1484,10 +1506,10 @@ test("model defaults tool_call to true when not specified", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("default-tools")].models["model"].capabilities.toolcall).toBe(true) }, }) @@ -1523,10 +1545,10 @@ test("model headers are preserved", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) const model = providers[ProviderID.make("headers-provider")].models["model"] expect(model.headers).toEqual({ "X-Custom-Header": "custom-value", @@ -1562,12 +1584,12 @@ test("provider env fallback - second env var used if first missing", async () => ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { + fn: async (ctx) => { // Only set fallback, not primary - set("FALLBACK_KEY", "fallback-api-key") - const providers = await list() + set(ctx, "FALLBACK_KEY", "fallback-api-key") + const providers = await list(ctx) // Provider should load because fallback env var is set expect(providers[ProviderID.make("fallback-env")]).toBeDefined() }, @@ -1585,12 +1607,12 @@ test("getModel returns consistent results", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const model1 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514")) - const model2 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514")) + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const model1 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"), ctx) + const model2 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"), ctx) expect(model1.providerID).toEqual(model2.providerID) expect(model1.id).toEqual(model2.id) expect(model1).toEqual(model2) @@ -1624,10 +1646,10 @@ test("provider name defaults to id when not in database", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("my-custom-id")].name).toBe("my-custom-id") }, }) @@ -1644,12 +1666,12 @@ test("ModelNotFoundError includes suggestions for typos", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") try { - await getModel(ProviderID.anthropic, ModelID.make("claude-sonet-4")) // typo: sonet instead of sonnet + await getModel(ProviderID.anthropic, ModelID.make("claude-sonet-4"), ctx) // typo: sonet instead of sonnet expect(true).toBe(false) // Should not reach here } catch (e: any) { expect(e.suggestions).toBeDefined() @@ -1670,12 +1692,12 @@ test("ModelNotFoundError for provider includes suggestions", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") try { - await getModel(ProviderID.make("antropic"), ModelID.make("claude-sonnet-4")) // typo: antropic + await getModel(ProviderID.make("antropic"), ModelID.make("claude-sonnet-4"), ctx) // typo: antropic expect(true).toBe(false) // Should not reach here } catch (e: any) { expect(e.suggestions).toBeDefined() @@ -1696,12 +1718,12 @@ test("ModelNotFoundError suggests catalog models for unloaded providers", async ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - remove("OPENCODE_API_KEY") + fn: async (ctx) => { + remove(ctx, "OPENCODE_API_KEY") try { - await getModel(ProviderID.opencode, ModelID.make("claude-haiku-fake-model")) + await getModel(ProviderID.opencode, ModelID.make("claude-haiku-fake-model"), ctx) throw new Error("expected model lookup to fail") } catch (e) { if (!Provider.ModelNotFoundError.isInstance(e)) throw e @@ -1722,10 +1744,10 @@ test("getProvider returns undefined for nonexistent provider", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const provider = await getProvider(ProviderID.make("nonexistent")) + fn: async (ctx) => { + const provider = await getProvider(ProviderID.make("nonexistent"), ctx) expect(provider).toBeUndefined() }, }) @@ -1742,11 +1764,11 @@ test("getProvider returns provider info", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const provider = await getProvider(ProviderID.anthropic) + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const provider = await getProvider(ProviderID.anthropic, ctx) expect(provider).toBeDefined() expect(String(provider?.id)).toBe("anthropic") }, @@ -1764,11 +1786,11 @@ test("closest returns undefined when no partial match found", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const result = await closest(ProviderID.anthropic, ["nonexistent-xyz-model"]) + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const result = await closest(ProviderID.anthropic, ["nonexistent-xyz-model"], ctx) expect(result).toBeUndefined() }, }) @@ -1785,12 +1807,12 @@ test("closest checks multiple query terms in order", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") // First term won't match, second will - const result = await closest(ProviderID.anthropic, ["nonexistent", "haiku"]) + const result = await closest(ProviderID.anthropic, ["nonexistent", "haiku"], ctx) expect(result).toBeDefined() expect(result?.modelID).toContain("haiku") }, @@ -1823,10 +1845,10 @@ test("model limit defaults to zero when not specified", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) const model = providers[ProviderID.make("no-limit")].models["model"] expect(model.limit.context).toBe(0) expect(model.limit.output).toBe(0) @@ -1855,11 +1877,11 @@ test("provider options are deeply merged", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) // Custom options should be merged expect(providers[ProviderID.anthropic].options.timeout).toBe(30000) expect(providers[ProviderID.anthropic].options.headers["X-Custom"]).toBe("custom-value") @@ -1887,10 +1909,10 @@ test("hosted nvidia provider adds billing origin header", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("nvidia")].options.headers).toEqual({ "HTTP-Referer": "https://kilo.ai/", "X-Title": "Kilo Code", @@ -1919,10 +1941,10 @@ test("custom nvidia baseURL adds billing origin header", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("nvidia")].options.headers).toEqual({ "HTTP-Referer": "https://kilo.ai/", "X-Title": "Kilo Code", @@ -1954,10 +1976,10 @@ test("explicit nvidia billing origin header is preserved", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) expect(providers[ProviderID.make("nvidia")].options.headers["X-BILLING-INVOKE-ORIGIN"]).toBe("CustomOrigin") }, }) @@ -1985,11 +2007,11 @@ test("custom model inherits npm package from models.dev provider config", async ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("OPENAI_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "OPENAI_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.openai].models["my-custom-model"] expect(model).toBeDefined() expect(model.api.npm).toBe("@ai-sdk/openai") @@ -2018,11 +2040,11 @@ test("custom model inherits api.url from models.dev provider", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("OPENROUTER_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "OPENROUTER_API_KEY", "test-api-key") + const providers = await list(ctx) expect(providers[ProviderID.openrouter]).toBeDefined() // New model not in database should inherit api.url from provider @@ -2149,11 +2171,11 @@ test("model variants are generated for reasoning models", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) // Claude sonnet 4 has reasoning capability const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] expect(model.capabilities.reasoning).toBe(true) @@ -2185,11 +2207,11 @@ test("model variants can be disabled via config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] expect(model.variants).toBeDefined() expect(model.variants!["high"]).toBeUndefined() @@ -2226,11 +2248,11 @@ test("model variants can be customized via config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] expect(model.variants!["high"]).toBeDefined() expect(model.variants!["high"].thinking.budgetTokens).toBe(20000) @@ -2263,11 +2285,11 @@ test("disabled key is stripped from variant config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] expect(model.variants!["max"]).toBeDefined() expect(model.variants!["max"].disabled).toBeUndefined() @@ -2299,11 +2321,11 @@ test("all variants can be disabled via config", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] expect(model.variants).toBeDefined() expect(Object.keys(model.variants!).length).toBe(0) @@ -2335,11 +2357,11 @@ test("variant config merges with generated variants", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] expect(model.variants!["high"]).toBeDefined() // Should have both the generated thinking config and the custom option @@ -2371,11 +2393,11 @@ test("variants filtered in second pass for database models", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("OPENAI_API_KEY", "test-api-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "OPENAI_API_KEY", "test-api-key") + const providers = await list(ctx) const model = providers[ProviderID.openai].models["gpt-5"] expect(model.variants).toBeDefined() expect(model.variants!["high"]).toBeUndefined() @@ -2418,10 +2440,10 @@ test("custom model with variants enabled and disabled", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const providers = await list() + fn: async (ctx) => { + const providers = await list(ctx) const model = providers[ProviderID.make("custom-reasoning")].models["reasoning-model"] expect(model.variants).toBeDefined() // Enabled variants should exist @@ -2448,7 +2470,7 @@ test("Google Vertex: retains baseURL for custom proxy", async () => { await Bun.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", provider: { "vertex-proxy": { name: "Vertex Proxy", @@ -2473,11 +2495,11 @@ test("Google Vertex: retains baseURL for custom proxy", async () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") - const providers = await list() + fn: async (ctx) => { + set(ctx, "GOOGLE_APPLICATION_CREDENTIALS", "test-creds") + const providers = await list(ctx) expect(providers[ProviderID.make("vertex-proxy")]).toBeDefined() expect(providers[ProviderID.make("vertex-proxy")].options.baseURL).toBe("https://my-proxy.com/v1") }, @@ -2490,7 +2512,7 @@ test("Google Vertex: supports OpenAI compatible models", async () => { await Bun.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", provider: { "vertex-openai": { name: "Vertex OpenAI", @@ -2516,11 +2538,11 @@ test("Google Vertex: supports OpenAI compatible models", async () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") - const providers = await list() + fn: async (ctx) => { + set(ctx, "GOOGLE_APPLICATION_CREDENTIALS", "test-creds") + const providers = await list(ctx) const model = providers[ProviderID.make("vertex-openai")].models["gpt-4"] expect(model).toBeDefined() @@ -2535,18 +2557,18 @@ test("cloudflare-ai-gateway loads with env variables", async () => { await Bun.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", }), ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("CLOUDFLARE_ACCOUNT_ID", "test-account") - set("CLOUDFLARE_GATEWAY_ID", "test-gateway") - set("CLOUDFLARE_API_TOKEN", "test-token") - const providers = await list() + fn: async (ctx) => { + set(ctx, "CLOUDFLARE_ACCOUNT_ID", "test-account") + set(ctx, "CLOUDFLARE_GATEWAY_ID", "test-gateway") + set(ctx, "CLOUDFLARE_API_TOKEN", "test-token") + const providers = await list(ctx) expect(providers[ProviderID.make("cloudflare-ai-gateway")]).toBeDefined() }, }) @@ -2558,7 +2580,7 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => { await Bun.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", provider: { "cloudflare-ai-gateway": { options: { @@ -2570,13 +2592,13 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => { ) }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("CLOUDFLARE_ACCOUNT_ID", "test-account") - set("CLOUDFLARE_GATEWAY_ID", "test-gateway") - set("CLOUDFLARE_API_TOKEN", "test-token") - const providers = await list() + fn: async (ctx) => { + set(ctx, "CLOUDFLARE_ACCOUNT_ID", "test-account") + set(ctx, "CLOUDFLARE_GATEWAY_ID", "test-gateway") + set(ctx, "CLOUDFLARE_API_TOKEN", "test-token") + const providers = await list(ctx) expect(providers[ProviderID.make("cloudflare-ai-gateway")]).toBeDefined() expect(providers[ProviderID.make("cloudflare-ai-gateway")].options.metadata).toEqual({ invoked_by: "test", @@ -2623,29 +2645,26 @@ test("plugin config providers persist after instance dispose", async () => { }, }) - const first = await WithInstance.provide({ + const first = await withTestInstance({ directory: tmp.path, - fn: async () => + fn: async (ctx) => AppRuntime.runPromise( Effect.gen(function* () { const plugin = yield* Plugin.Service const provider = yield* Provider.Service yield* plugin.init() return yield* provider.list() - }), + }).pipe(Effect.provideService(InstanceRef, ctx)), ), }) expect(first[ProviderID.make("demo")]).toBeDefined() expect(first[ProviderID.make("demo")].models[ModelID.make("chat")]).toBeDefined() - await WithInstance.provide({ - directory: tmp.path, - fn: () => InstanceRuntime.disposeInstance(Instance.current), - }) + await disposeAllInstances() - const second = await WithInstance.provide({ + const second = await withTestInstance({ directory: tmp.path, - fn: async () => list(), + fn: async (ctx) => list(ctx), }) expect(second[ProviderID.make("demo")]).toBeDefined() expect(second[ProviderID.make("demo")].models[ModelID.make("chat")]).toBeDefined() @@ -2676,12 +2695,12 @@ test("plugin config enabled and disabled providers are honored", async () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-anthropic-key") - set("OPENAI_API_KEY", "test-openai-key") - const providers = await list() + fn: async (ctx) => { + set(ctx, "ANTHROPIC_API_KEY", "test-anthropic-key") + set(ctx, "OPENAI_API_KEY", "test-openai-key") + const providers = await list(ctx) expect(providers[ProviderID.anthropic]).toBeDefined() expect(providers[ProviderID.openai]).toBeUndefined() }, @@ -2694,15 +2713,15 @@ test("opencode loader keeps paid models when config apiKey is present", async () await Bun.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", }), ) }, }) - const none = await WithInstance.provide({ + const none = await withTestInstance({ directory: base.path, - fn: async () => paid(await list()), + fn: async (ctx) => paid(await list(ctx)), }) await using keyed = await tmpdir({ @@ -2710,7 +2729,7 @@ test("opencode loader keeps paid models when config apiKey is present", async () await Bun.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", provider: { opencode: { options: { @@ -2723,9 +2742,9 @@ test("opencode loader keeps paid models when config apiKey is present", async () }, }) - const keyedCount = await WithInstance.provide({ + const keyedCount = await withTestInstance({ directory: keyed.path, - fn: async () => paid(await list()), + fn: async (ctx) => paid(await list(ctx)), }) expect(none).toBe(0) @@ -2738,15 +2757,15 @@ test("opencode loader keeps paid models when auth exists", async () => { await Bun.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", }), ) }, }) - const none = await WithInstance.provide({ + const none = await withTestInstance({ directory: base.path, - fn: async () => paid(await list()), + fn: async (ctx) => paid(await list(ctx)), }) await using keyed = await tmpdir({ @@ -2754,7 +2773,7 @@ test("opencode loader keeps paid models when auth exists", async () => { await Bun.write( path.join(dir, "opencode.json"), JSON.stringify({ - $schema: "https://opencode.ai/config.json", // kilocode_change + $schema: "https://opencode.ai/config.json", }), ) }, @@ -2778,9 +2797,9 @@ test("opencode loader keeps paid models when auth exists", async () => { }), ) - const keyedCount = await WithInstance.provide({ + const keyedCount = await withTestInstance({ directory: keyed.path, - fn: async () => paid(await list()), + fn: async (ctx) => paid(await list(ctx)), }) expect(none).toBe(0) diff --git a/packages/opencode/test/question/question.test.ts b/packages/opencode/test/question/question.test.ts index 37a1d534f5..40b4e8d984 100644 --- a/packages/opencode/test/question/question.test.ts +++ b/packages/opencode/test/question/question.test.ts @@ -1,10 +1,10 @@ -import { afterEach, expect } from "bun:test" // kilocode_change - blocking behavior now uses the scoped service test helper +import { afterEach, expect } from "bun:test" import { Cause, Effect, Exit, Fiber, Layer, Queue } from "effect" import { Question } from "../../src/question" -import { Instance } from "../../src/project/instance" +import { InstanceRef } from "../../src/effect/instance-ref" import { InstanceRuntime } from "../../src/project/instance-runtime" import { QuestionID } from "../../src/question/schema" -import { disposeAllInstances, provideInstance, reloadTestInstance, tmpdirScoped } from "../fixture/fixture" // kilocode_change - blocking coverage no longer uses the Promise facade fixture +import { disposeAllInstances, provideInstance, reloadTestInstance, tmpdirScoped } from "../fixture/fixture" import { SessionID } from "../../src/session/schema" import { testEffect } from "../lib/effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" @@ -454,7 +454,10 @@ it.live("pending question rejects on instance dispose", () => }).pipe(provideInstance(dir), Effect.forkScoped) expect(yield* waitForPending(1).pipe(provideInstance(dir))).toHaveLength(1) - const ctx = yield* Effect.sync(() => Instance.current).pipe(provideInstance(dir)) + const ctx = yield* Effect.gen(function* () { + return yield* InstanceRef + }).pipe(provideInstance(dir)) + if (!ctx) return yield* Effect.die(new Error("missing test instance")) yield* Effect.promise(() => InstanceRuntime.disposeInstance(ctx)) const exit = yield* Fiber.await(fiber) diff --git a/packages/opencode/test/server/experimental-session-list.test.ts b/packages/opencode/test/server/experimental-session-list.test.ts index 0292057367..c4f01d8494 100644 --- a/packages/opencode/test/server/experimental-session-list.test.ts +++ b/packages/opencode/test/server/experimental-session-list.test.ts @@ -2,13 +2,13 @@ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test" import { $ } from "bun" import path from "path" -import { WithInstance } from "../../src/project/with-instance" +import type { InstanceContext } from "../../src/project/instance-context" +import { InstanceRef } from "../../src/effect/instance-ref" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" -import { tmpdir } from "../fixture/fixture" +import { tmpdir, withTestInstance } from "../fixture/fixture" import { RemoteSender } from "../../src/kilo-sessions/remote-sender" import { Effect } from "effect" -import { Session } from "../../src/session/session" beforeEach(() => { spyOn(RemoteSender, "create").mockReturnValue({ handle() {}, dispose() {} }) @@ -21,8 +21,15 @@ afterEach(async () => { await resetDatabase() }) -const create = (title: string) => - Effect.runPromise(Session.Service.use((svc) => svc.create({ title })).pipe(Effect.provide(Session.defaultLayer))) +const create = async (title: string, ctx: InstanceContext) => { + const [{ AppRuntime }, { Session }] = await Promise.all([ + import("../../src/effect/app-runtime"), + import("../../src/session/session"), + ]) + return AppRuntime.runPromise( + Session.Service.use((svc) => svc.create({ title })).pipe(Effect.provideService(InstanceRef, ctx)), + ) +} describe("experimental.session.list", () => { test("filters sessions by repo worktree family even when project IDs drift", async () => { @@ -37,29 +44,29 @@ describe("experimental.session.list", () => { const { Server } = await import("../../src/server/server") // Create worktree session first so it computes its own project ID via rev-list - const branch = await WithInstance.provide({ + const branch = await withTestInstance({ directory: worktree, - fn: () => create("worktree-session"), + fn: (ctx) => create("worktree-session", ctx), }) // Now write a stale project ID to .git/kilo — this overrides the root's cached ID await Bun.write(path.join(first.path, ".git", "kilo"), "stale-project-id") - const root = await WithInstance.provide({ + const root = await withTestInstance({ directory: first.path, - fn: async () => ({ + fn: async (ctx) => ({ app: Server.Default().app, project: await Server.Default().app.request("/project/current", { headers: { "x-kilo-directory": first.path }, }), - session: await create("root-session"), + session: await create("root-session", ctx), }), }) await Bun.file(path.join(first.path, ".git", "kilo")).delete() - await WithInstance.provide({ + await withTestInstance({ directory: second.path, - fn: () => create("other-project-session"), + fn: (ctx) => create("other-project-session", ctx), }) const app = root.app @@ -101,25 +108,25 @@ describe("experimental.session.list", () => { try { const { Server } = await import("../../src/server/server") - const branch = await WithInstance.provide({ + const branch = await withTestInstance({ directory: worktree, - fn: () => create("worktree-session"), + fn: (ctx) => create("worktree-session", ctx), }) - const root = await WithInstance.provide({ + const root = await withTestInstance({ directory: first.path, - fn: async () => ({ + fn: async (ctx) => ({ app: Server.Default().app, project: await Server.Default().app.request("/project/current", { headers: { "x-kilo-directory": first.path }, }), - session: await create("root-session"), + session: await create("root-session", ctx), }), }) - await WithInstance.provide({ + await withTestInstance({ directory: second.path, - fn: () => create("other-project-session"), + fn: (ctx) => create("other-project-session", ctx), }) const app = root.app diff --git a/packages/opencode/test/server/httpapi-error-middleware.test.ts b/packages/opencode/test/server/httpapi-error-middleware.test.ts index 15f8aa2026..51d4cf9e0c 100644 --- a/packages/opencode/test/server/httpapi-error-middleware.test.ts +++ b/packages/opencode/test/server/httpapi-error-middleware.test.ts @@ -1,6 +1,7 @@ import { NodeHttpServer, NodeServices } from "@effect/platform-node" import { NamedError } from "@opencode-ai/core/util/error" import { describe, expect } from "bun:test" +import { ConfigError } from "../../src/config/error" import { Effect, Layer } from "effect" import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http" import { errorLayer } from "../../src/server/routes/instance/httpapi/middleware/error" @@ -50,6 +51,27 @@ describe("HttpApi error middleware", () => { }), ) + it.live("preserves config defects as client-visible bad requests", () => + Effect.gen(function* () { + const configError = new ConfigError.InvalidError({ + path: "/tmp/opencode.json", + issues: [{ message: "Expected object", path: ["provider", "anthropic", "options"] }], + }) + + yield* HttpRouter.add("GET", "/config-error", Effect.die(configError)).pipe( + Layer.provide(errorLayer), + HttpRouter.serve, + Layer.build, + ) + + const response = yield* HttpClientRequest.get("/config-error").pipe(HttpClient.execute) + const body = yield* response.json + + expect(response.status).toBe(400) + expect(JSON.stringify(body)).toBe(JSON.stringify(configError.toObject())) + }), + ) + it.live("does not map storage not-found defects to 404", () => Effect.gen(function* () { yield* HttpRouter.add( diff --git a/packages/opencode/test/server/httpapi-event.test.ts b/packages/opencode/test/server/httpapi-event.test.ts index 5ad2926f27..20d9d52126 100644 --- a/packages/opencode/test/server/httpapi-event.test.ts +++ b/packages/opencode/test/server/httpapi-event.test.ts @@ -1,11 +1,21 @@ import { afterEach, describe, expect, test } from "bun:test" import { Bus } from "../../src/bus" -import { Instance } from "../../src/project/instance" +import { AppRuntime } from "../../src/effect/app-runtime" +import { InstanceRef } from "../../src/effect/instance-ref" import { Server } from "../../src/server/server" import { EventPaths } from "../../src/server/routes/instance/httpapi/groups/event" import { Event as ServerEvent } from "../../src/server/event" +// kilocode_change start - verify transformed EventV2 values at the legacy SSE boundary +import { Catalog } from "@opencode-ai/core/catalog" +import { EventV2 } from "@opencode-ai/core/event" +import { ModelV2 } from "@opencode-ai/core/model" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { SessionEvent } from "@opencode-ai/core/session-event" +import { GlobalPaths } from "../../src/server/routes/instance/httpapi/groups/global" +import { SessionID } from "../../src/session/schema" +// kilocode_change end import * as Log from "@opencode-ai/core/util/log" -import { Schema } from "effect" +import { DateTime, Effect, Schema } from "effect" import { resetDatabase } from "../fixture/db" import { disposeAllInstances, reloadTestInstance, tmpdir } from "../fixture/fixture" @@ -21,6 +31,17 @@ const EventData = Schema.Struct({ properties: Schema.Record(Schema.String, Schema.Any), }) +// kilocode_change start - inspect the real global SSE envelope +const GlobalEventData = Schema.Struct({ + directory: Schema.optional(Schema.String), + payload: Schema.Struct({ + id: Schema.optional(Schema.String), + type: Schema.String, + properties: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + }), +}) +// kilocode_change end + async function readChunk(reader: ReadableStreamDefaultReader, delay = 5_000) { let timeout: ReturnType | undefined try { @@ -89,6 +110,33 @@ async function readEventWithin(reader: ReadableStreamDefaultReader, return Schema.decodeUnknownSync(EventData)(JSON.parse(new TextDecoder().decode(result.value).replace(/^data: /, ""))) } +// kilocode_change start - read transformed values from the global SSE wire payload +async function readGlobal(reader: ReadableStreamDefaultReader, delay = 5_000) { + const result = await readChunk(reader, delay) + if (result.done || !result.value) throw new Error("global event stream closed") + return Schema.decodeUnknownSync(GlobalEventData)( + JSON.parse(new TextDecoder().decode(result.value).replace(/^data: /, "")), + ) +} + +function properties(event: Schema.Schema.Type) { + if (!event.payload.properties) throw new Error(`event ${event.payload.type} has no properties`) + return event.payload.properties +} + +async function readGlobalUntil( + reader: ReadableStreamDefaultReader, + predicate: (event: Schema.Schema.Type) => boolean, + delay = 5_000, +) { + const end = Date.now() + delay + while (true) { + const event = await readGlobal(reader, end - Date.now()) + if (predicate(event)) return event + } +} +// kilocode_change end + afterEach(async () => { await disposeAllInstances() await resetDatabase() @@ -133,11 +181,81 @@ describe("event HttpApi", () => { const id = Bus.createID() const next = readUntil(reader, (event) => event.id === id && event.type === "server.connected") const ctx = await reloadTestInstance({ directory: tmp.path }) - await Instance.restore(ctx, () => Bus.publish(ServerEvent.Connected, {}, { id })) + await AppRuntime.runPromise( + Bus.Service.use((svc) => svc.publish(ServerEvent.Connected, {}, { id })).pipe( + Effect.provideService(InstanceRef, ctx), + ), + ) expect(await next).toMatchObject({ id, type: "server.connected", properties: {} }) } finally { await reader.cancel() } }) + + // kilocode_change start - transformed EventV2 data is numeric on legacy SSE while domain data stays decoded + test("encodes catalog and session EventV2 data on the global event stream", async () => { + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) + const response = await app().request(GlobalPaths.event) + if (!response.body) throw new Error("missing response body") + + const reader = response.body.getReader() + try { + expect(await readGlobal(reader)).toMatchObject({ payload: { type: "server.connected", properties: {} } }) + const ctx = await reloadTestInstance({ directory: tmp.path }) + const released = DateTime.makeUnsafe(1_750_000_000_123) + const model = new ModelV2.Info({ + ...ModelV2.Info.empty(ProviderV2.ID.make("test"), ModelV2.ID.make("model")), + time: { released }, + }) + const catalogID = EventV2.ID.create() + const catalog = readGlobalUntil(reader, (event) => event.payload.id === catalogID) + const catalogDomain = await AppRuntime.runPromise( + EventV2.Service.use((events) => events.publish(Catalog.Event.ModelUpdated, { model }, { id: catalogID })).pipe( + Effect.provideService(InstanceRef, ctx), + ), + ) + + expect(DateTime.isDateTime(catalogDomain.data.model.time.released)).toBe(true) + expect(properties(await catalog).model.time.released).toBe(1_750_000_000_123) + + const globalID = EventV2.ID.create() + const global = readGlobalUntil(reader, (event) => event.payload.id === globalID) + await AppRuntime.runPromise( + EventV2.Service.use((events) => events.publish(Catalog.Event.ModelUpdated, { model }, { id: globalID })), + ) + expect((await global).directory).toBe("global") + + const timestamp = DateTime.makeUnsafe(1_234) + const sessionID = SessionID.make("ses_event_encoding") + const session = readGlobalUntil( + reader, + (event) => + event.payload.type === SessionEvent.Text.Delta.type && properties(event).sessionID === sessionID, + ) + const sessionDomain = await AppRuntime.runPromise( + EventV2.Service.use((events) => + events.publish(SessionEvent.Text.Delta, { sessionID, timestamp, delta: "hello" }), + ).pipe(Effect.provideService(InstanceRef, ctx)), + ) + + expect(DateTime.isDateTime(sessionDomain.data.timestamp)).toBe(true) + expect(properties(await session).timestamp).toBe(1_234) + + const prompted = readGlobalUntil(reader, (event) => event.payload.type === SessionEvent.Prompted.type) + await AppRuntime.runPromise( + EventV2.Service.use((events) => + events.publish(SessionEvent.Prompted, { + sessionID, + timestamp, + prompt: { text: "hello", files: [], agents: [], references: [] }, + }), + ).pipe(Effect.provideService(InstanceRef, ctx)), + ) + expect(properties(await prompted)).toMatchObject({ timestamp: 1_234, prompt: { text: "hello" } }) + } finally { + await reader.cancel() + } + }) + // kilocode_change end }) diff --git a/packages/opencode/test/server/httpapi-exercise/runtime.ts b/packages/opencode/test/server/httpapi-exercise/runtime.ts index 9d2b6d238e..2a7c6e080b 100644 --- a/packages/opencode/test/server/httpapi-exercise/runtime.ts +++ b/packages/opencode/test/server/httpapi-exercise/runtime.ts @@ -3,7 +3,6 @@ export type Runtime = { HttpApiApp: (typeof import("../../../src/server/routes/instance/httpapi/server"))["HttpApiApp"] AppLayer: (typeof import("../../../src/effect/app-runtime"))["AppLayer"] InstanceRef: (typeof import("../../../src/effect/instance-ref"))["InstanceRef"] - Instance: (typeof import("../../../src/project/instance"))["Instance"] InstanceStore: (typeof import("../../../src/project/instance-store"))["InstanceStore"] Session: (typeof import("../../../src/session/session"))["Session"] Todo: (typeof import("../../../src/session/todo"))["Todo"] @@ -22,7 +21,6 @@ export function runtime() { const httpApiServer = await import("../../../src/server/routes/instance/httpapi/server") const appRuntime = await import("../../../src/effect/app-runtime") const instanceRef = await import("../../../src/effect/instance-ref") - const instance = await import("../../../src/project/instance") const instanceStore = await import("../../../src/project/instance-store") const session = await import("../../../src/session/session") const todo = await import("../../../src/session/todo") @@ -35,7 +33,6 @@ export function runtime() { HttpApiApp: httpApiServer.HttpApiApp, AppLayer: appRuntime.AppLayer, InstanceRef: instanceRef.InstanceRef, - Instance: instance.Instance, InstanceStore: instanceStore.InstanceStore, Session: session.Session, Todo: todo.Todo, diff --git a/packages/opencode/test/server/httpapi-file.test.ts b/packages/opencode/test/server/httpapi-file.test.ts index 442a996a29..10a4289e18 100644 --- a/packages/opencode/test/server/httpapi-file.test.ts +++ b/packages/opencode/test/server/httpapi-file.test.ts @@ -3,7 +3,6 @@ import { Context } from "effect" import path from "path" import { HttpApiApp } from "../../src/server/routes/instance/httpapi/server" import { FilePaths } from "../../src/server/routes/instance/httpapi/groups/file" -import { Instance } from "../../src/project/instance" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" import { disposeAllInstances, tmpdir } from "../fixture/fixture" diff --git a/packages/opencode/test/server/httpapi-instance-context.test.ts b/packages/opencode/test/server/httpapi-instance-context.test.ts index 4e2cc9d8ae..27c91808ea 100644 --- a/packages/opencode/test/server/httpapi-instance-context.test.ts +++ b/packages/opencode/test/server/httpapi-instance-context.test.ts @@ -10,7 +10,6 @@ import { WorkspaceID } from "../../src/control-plane/schema" import type { WorkspaceAdapter } from "../../src/control-plane/types" import { Workspace } from "../../src/control-plane/workspace" import { InstanceRef, WorkspaceRef } from "../../src/effect/instance-ref" -import { Instance } from "../../src/project/instance" import { InstanceLayer } from "../../src/project/instance-layer" import { Project } from "../../src/project/project" import { disposeMiddleware, markInstanceForDisposal } from "../../src/server/routes/instance/httpapi/lifecycle" diff --git a/packages/opencode/test/server/httpapi-pty.test.ts b/packages/opencode/test/server/httpapi-pty.test.ts index 62758727a5..bf2ed2a7cb 100644 --- a/packages/opencode/test/server/httpapi-pty.test.ts +++ b/packages/opencode/test/server/httpapi-pty.test.ts @@ -1,7 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test" import { NodeHttpServer, NodeServices } from "@effect/platform-node" import { PtyID } from "../../src/pty/schema" -import { Instance } from "../../src/project/instance" import { Server } from "../../src/server/server" import { PtyPaths } from "../../src/server/routes/instance/httpapi/groups/pty" import * as Log from "@opencode-ai/core/util/log" diff --git a/packages/opencode/test/server/httpapi-raw-route-auth.test.ts b/packages/opencode/test/server/httpapi-raw-route-auth.test.ts index 102bcc8f50..f8ee06f19d 100644 --- a/packages/opencode/test/server/httpapi-raw-route-auth.test.ts +++ b/packages/opencode/test/server/httpapi-raw-route-auth.test.ts @@ -1,7 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test" import { ConfigProvider, Layer } from "effect" import { HttpRouter } from "effect/unstable/http" -import { Instance } from "../../src/project/instance" import { EventPaths } from "../../src/server/routes/instance/httpapi/groups/event" import { PtyPaths } from "../../src/server/routes/instance/httpapi/groups/pty" import { HttpApiApp } from "../../src/server/routes/instance/httpapi/server" diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 4b6feb7e1b..7970c4c0f7 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -19,7 +19,7 @@ import { MessageID, PartID, SessionID, type SessionID as SessionIDType } from ". import { MessageV2 } from "../../src/session/message-v2" import { Database } from "@/storage/db" import { SessionMessageTable, SessionTable } from "@/session/session.sql" -import { SessionMessage } from "../../src/v2/session-message" +import { SessionMessage } from "@opencode-ai/core/session-message" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" import * as DateTime from "effect/DateTime" diff --git a/packages/opencode/test/server/httpapi-workspace.test.ts b/packages/opencode/test/server/httpapi-workspace.test.ts index 2f72d3bbe5..1862c11bc1 100644 --- a/packages/opencode/test/server/httpapi-workspace.test.ts +++ b/packages/opencode/test/server/httpapi-workspace.test.ts @@ -13,7 +13,6 @@ import * as Log from "@opencode-ai/core/util/log" import { Server } from "../../src/server/server" import { resetDatabase } from "../fixture/db" import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/fixture" -import { Instance } from "../../src/project/instance" import { InstanceBootstrap } from "../../src/project/bootstrap" import { InstanceStore } from "../../src/project/instance-store" import { Project } from "../../src/project/project" @@ -71,7 +70,7 @@ function listedAdapter(directory: string, type: string): WorkspaceAdapter { }, async create() {}, async remove() {}, - list() { + list(context) { return [ { type, @@ -79,7 +78,7 @@ function listedAdapter(directory: string, type: string): WorkspaceAdapter { branch: "listed/main", directory, extra: { listed: true }, - projectID: Instance.project.id, + projectID: context?.instance?.project.id ?? missingAdapterContext(), }, ] }, @@ -92,6 +91,10 @@ function listedAdapter(directory: string, type: string): WorkspaceAdapter { } } +function missingAdapterContext(): never { + throw new Error("missing workspace adapter context") +} + function remoteAdapter(directory: string, url: string, headers?: HeadersInit): WorkspaceAdapter { return { name: "Remote Test", diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index f43340e07a..b2270ace5b 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -29,6 +29,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { TestConfig } from "../fixture/config" import { SyncEvent } from "@/sync" import { RuntimeFlags } from "@/effect/runtime-flags" +import { EventV2Bridge } from "@/event-v2-bridge" void Log.init({ print: false }) @@ -225,6 +226,8 @@ const deps = Layer.mergeAll( Bus.layer, Config.defaultLayer, SyncEvent.defaultLayer, + RuntimeFlags.layer({ experimentalEventSystem: true }), + EventV2Bridge.defaultLayer, ).pipe(Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true }))) const env = Layer.mergeAll( @@ -246,6 +249,7 @@ type CompactionProcessOptions = { plugin?: Layer.Layer provider?: ReturnType config?: Layer.Layer + flags?: Partial // kilocode_change } function withCompaction(options?: CompactionProcessOptions) { @@ -274,6 +278,8 @@ function compactionProcessLayer(options?: CompactionProcessOptions) { Layer.provide(bus), Layer.provide(options?.config ?? Config.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true, ...options?.flags })), // kilocode_change + Layer.provide(EventV2Bridge.defaultLayer), ) } @@ -953,6 +959,40 @@ describe("session.compaction.process", () => { }).pipe(withCompaction({ config: cfg({ tail_turns: 2, preserve_recent_tokens: 10_000 }) })), ) + // kilocode_change start - configured output ceiling controls automatic tail budgeting + itCompaction.instance( + "uses the configured output token ceiling for retained tail budgeting", + Effect.gen(function* () { + const ssn = yield* SessionNs.Service + const session = yield* ssn.create({}) + yield* createUserMessage(session.id, "first") + const keep = yield* createUserMessage(session.id, "x".repeat(12_000)) + yield* createUserMessage(session.id, "third") + yield* createSummaryCompaction(session.id) + + const msgs = yield* ssn.messages({ sessionID: session.id }) + const parent = msgs.at(-1)?.info.id + expect(parent).toBeTruthy() + if (!parent) return yield* Effect.die(new Error("compaction parent not found")) + yield* SessionCompaction.use.process({ + parentID: parent, + messages: msgs, + sessionID: session.id, + auto: false, + }) + + const part = yield* readCompactionPart(session.id) + expect(part?.tail_start_id).toBe(keep.id) + }).pipe( + withCompaction({ + config: cfg({ tail_turns: 2 }), + provider: ProviderTest.fake({ model: createModel({ context: 20_000, output: 10_000 }) }), + flags: { outputTokenMax: 2_000 }, + }), + ), + ) + // kilocode_change end + itCompaction.instance( "shrinks retained tail to fit preserve token budget", Effect.gen(function* () { diff --git a/packages/opencode/test/session/instruction.test.ts b/packages/opencode/test/session/instruction.test.ts index 5d40933954..0f9c340dd4 100644 --- a/packages/opencode/test/session/instruction.test.ts +++ b/packages/opencode/test/session/instruction.test.ts @@ -10,6 +10,7 @@ import { Instruction } from "../../src/session/instruction" import type { MessageV2 } from "../../src/session/message-v2" import { MessageID, PartID, SessionID } from "../../src/session/schema" import { Global } from "@opencode-ai/core/global" +import { RuntimeFlags } from "../../src/effect/runtime-flags" import { provideInstance, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { TestConfig } from "../fixture/config" @@ -18,18 +19,19 @@ const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, NodeFileSys const configLayer = TestConfig.layer() -const instructionLayer = (global: Partial) => +const instructionLayer = (global: Partial, flags: Partial = {}) => Instruction.layer.pipe( Layer.provide(configLayer), Layer.provide(AppFileSystem.defaultLayer), Layer.provide(FetchHttpClient.layer), Layer.provide(Global.layerWith(global)), + Layer.provide(RuntimeFlags.layer(flags)), ) const provideInstruction = - (global: Partial) => + (global: Partial, flags?: Partial) => (self: Effect.Effect) => - self.pipe(Effect.provide(instructionLayer(global))) + self.pipe(Effect.provide(instructionLayer(global, flags))) const write = (filepath: string, content: string) => Effect.gen(function* () { @@ -215,6 +217,24 @@ describe("Instruction.system", () => { }).pipe(provideInstance(projectTmp), provideInstruction({ home: globalTmp, config: globalTmp })) }), ) + + it.live("skips project and global CLAUDE.md when Claude Code prompt is disabled", () => + Effect.gen(function* () { + const globalTmp = yield* tmpWithFiles({ ".claude/CLAUDE.md": "# Global Claude" }) + const projectTmp = yield* tmpWithFiles({ "CLAUDE.md": "# Project Claude" }) + + yield* Effect.gen(function* () { + const svc = yield* Instruction.Service + const paths = yield* svc.systemPaths() + expect(paths.has(path.join(globalTmp, ".claude", "CLAUDE.md"))).toBe(false) + expect(paths.has(path.join(projectTmp, "CLAUDE.md"))).toBe(false) + expect(yield* svc.system()).toEqual([]) + }).pipe( + provideInstance(projectTmp), + provideInstruction({ home: globalTmp, config: globalTmp }, { disableClaudeCodePrompt: true }), + ) + }), + ) }) describe("Instruction.systemPaths global config", () => { diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index a6915282c8..04e7da6965 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -4,33 +4,35 @@ import { tool, type ModelMessage } from "ai" import { Cause, Effect, Exit, Stream } from "effect" import z from "zod" import { makeRuntime } from "../../src/effect/run-service" +import { InstanceRef } from "../../src/effect/instance-ref" import { LLM } from "../../src/session/llm" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import type { InstanceContext } from "../../src/project/instance-context" import { Provider } from "@/provider/provider" import { ProviderTransform } from "@/provider/transform" import { ModelsDev } from "@opencode-ai/core/models" import { ProviderID, ModelID } from "../../src/provider/schema" import { Filesystem } from "@/util/filesystem" -import { tmpdir } from "../fixture/fixture" +import { tmpdir, withTestInstance } from "../fixture/fixture" import type { Agent } from "../../src/agent/agent" import { MessageV2 } from "../../src/session/message-v2" import { SessionID, MessageID } from "../../src/session/schema" import { AppRuntime } from "../../src/effect/app-runtime" -async function getModel(providerID: ProviderID, modelID: ModelID) { - return AppRuntime.runPromise( - Effect.gen(function* () { - const provider = yield* Provider.Service - return yield* provider.getModel(providerID, modelID) - }), - ) +async function getModel(providerID: ProviderID, modelID: ModelID, ctx: InstanceContext) { + const effect = Effect.gen(function* () { + const provider = yield* Provider.Service + return yield* provider.getModel(providerID, modelID) + }) + return AppRuntime.runPromise(effect.pipe(Effect.provideService(InstanceRef, ctx))) } const llm = makeRuntime(LLM.Service, LLM.defaultLayer) -async function drain(input: LLM.StreamInput) { - return llm.runPromise((svc) => svc.stream(input).pipe(Stream.runDrain)) +async function drain(input: LLM.StreamInput, ctx: InstanceContext) { + return llm.runPromise((svc) => { + const effect = svc.stream(input).pipe(Stream.runDrain) + return effect.pipe(Effect.provideService(InstanceRef, ctx)) + }) } describe("session.llm.hasToolCalls", () => { @@ -358,10 +360,10 @@ describe("session.llm.stream", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id)) + fn: async (ctx) => { + const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id), ctx) const sessionID = SessionID.make("session-test-1") const agent = { name: "test", @@ -381,15 +383,18 @@ describe("session.llm.stream", () => { model: { providerID: ProviderID.make(providerID), modelID: resolved.id, variant: "high" }, } satisfies MessageV2.User - await drain({ - user, - sessionID, - model: resolved, - agent, - system: ["You are a helpful assistant."], - messages: [{ role: "user", content: "Hello" }], - tools: {}, - }) + await drain( + { + user, + sessionID, + model: resolved, + agent, + system: ["You are a helpful assistant."], + messages: [{ role: "user", content: "Hello" }], + tools: {}, + }, + ctx, + ) const capture = await request const body = capture.body @@ -446,10 +451,10 @@ describe("session.llm.stream", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id)) + fn: async (ctx) => { + const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id), ctx) const sessionID = SessionID.make("session-test-service-abort") const agent = { name: "test", @@ -479,7 +484,7 @@ describe("session.llm.stream", () => { messages: [{ role: "user", content: "Hello" }], tools: {}, }) - .pipe(Stream.runDrain), + .pipe(Stream.runDrain, Effect.provideService(InstanceRef, ctx)), { signal: ctrl.signal }, ) @@ -536,10 +541,10 @@ describe("session.llm.stream", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id)) + fn: async (ctx) => { + const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id), ctx) const sessionID = SessionID.make("session-test-tools") const agent = { name: "test", @@ -558,22 +563,25 @@ describe("session.llm.stream", () => { tools: { question: true }, } satisfies MessageV2.User - await drain({ - user, - sessionID, - model: resolved, - agent, - permission: [{ permission: "question", pattern: "*", action: "allow" }], - system: ["You are a helpful assistant."], - messages: [{ role: "user", content: "Hello" }], - tools: { - question: tool({ - description: "Ask a question", - inputSchema: z.object({}), - execute: async () => ({ output: "" }), - }), + await drain( + { + user, + sessionID, + model: resolved, + agent, + permission: [{ permission: "question", pattern: "*", action: "allow" }], + system: ["You are a helpful assistant."], + messages: [{ role: "user", content: "Hello" }], + tools: { + question: tool({ + description: "Ask a question", + inputSchema: z.object({}), + execute: async () => ({ output: "" }), + }), + }, }, - }) + ctx, + ) const capture = await request const tools = capture.body.tools as Array<{ function?: { name?: string } }> | undefined @@ -650,10 +658,10 @@ describe("session.llm.stream", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const resolved = await getModel(ProviderID.openai, ModelID.make(model.id)) + fn: async (ctx) => { + const resolved = await getModel(ProviderID.openai, ModelID.make(model.id), ctx) const sessionID = SessionID.make("session-test-2") const agent = { name: "test", @@ -672,15 +680,18 @@ describe("session.llm.stream", () => { model: { providerID: ProviderID.make("openai"), modelID: resolved.id, variant: "high" }, } satisfies MessageV2.User - await drain({ - user, - sessionID, - model: resolved, - agent, - system: ["You are a helpful assistant."], - messages: [{ role: "user", content: "Hello" }], - tools: {}, - }) + await drain( + { + user, + sessionID, + model: resolved, + agent, + system: ["You are a helpful assistant."], + messages: [{ role: "user", content: "Hello" }], + tools: {}, + }, + ctx, + ) const capture = await request const body = capture.body @@ -766,10 +777,10 @@ describe("session.llm.stream", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const resolved = await getModel(ProviderID.openai, ModelID.make(model.id)) + fn: async (ctx) => { + const resolved = await getModel(ProviderID.openai, ModelID.make(model.id), ctx) const sessionID = SessionID.make("session-test-data-url") const agent = { name: "test", @@ -787,28 +798,31 @@ describe("session.llm.stream", () => { model: { providerID: ProviderID.make("openai"), modelID: resolved.id }, } satisfies MessageV2.User - await drain({ - user, - sessionID, - model: resolved, - agent, - system: ["You are a helpful assistant."], - messages: [ - { - role: "user", - content: [ - { type: "text", text: "Describe this image" }, - { - type: "file", - mediaType: "image/png", - filename: "large-image.png", - data: image, - }, - ], - }, - ] as ModelMessage[], - tools: {}, - }) + await drain( + { + user, + sessionID, + model: resolved, + agent, + system: ["You are a helpful assistant."], + messages: [ + { + role: "user", + content: [ + { type: "text", text: "Describe this image" }, + { + type: "file", + mediaType: "image/png", + filename: "large-image.png", + data: image, + }, + ], + }, + ] as ModelMessage[], + tools: {}, + }, + ctx, + ) const capture = await request expect(capture.url.pathname.endsWith("/responses")).toBe(true) @@ -885,10 +899,10 @@ describe("session.llm.stream", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id)) + fn: async (ctx) => { + const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id), ctx) const sessionID = SessionID.make("session-test-3") const agent = { name: "test", @@ -908,15 +922,18 @@ describe("session.llm.stream", () => { model: { providerID: ProviderID.make("minimax"), modelID: ModelID.make("MiniMax-M2.5") }, } satisfies MessageV2.User - await drain({ - user, - sessionID, - model: resolved, - agent, - system: ["You are a helpful assistant."], - messages: [{ role: "user", content: "Hello" }], - tools: {}, - }) + await drain( + { + user, + sessionID, + model: resolved, + agent, + system: ["You are a helpful assistant."], + messages: [{ role: "user", content: "Hello" }], + tools: {}, + }, + ctx, + ) const capture = await request const body = capture.body @@ -1003,10 +1020,10 @@ describe("session.llm.stream", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const resolved = await getModel(ProviderID.make("anthropic"), ModelID.make(model.id)) + fn: async (ctx) => { + const resolved = await getModel(ProviderID.make("anthropic"), ModelID.make(model.id), ctx) const sessionID = SessionID.make("session-test-anthropic-tools") const agent = { name: "test", @@ -1111,31 +1128,34 @@ describe("session.llm.stream", () => { }, ] as any[] - await drain({ - user, - sessionID, - model: resolved, - agent, - system: [], - messages: await MessageV2.toModelMessages(input as any, resolved), - tools: { - read: tool({ - description: "Stub read tool", - inputSchema: z.object({ - filePath: z.string(), + await drain( + { + user, + sessionID, + model: resolved, + agent, + system: [], + messages: await MessageV2.toModelMessages(input as any, resolved), + tools: { + read: tool({ + description: "Stub read tool", + inputSchema: z.object({ + filePath: z.string(), + }), + execute: async () => ({ output: "stub" }), }), - execute: async () => ({ output: "stub" }), - }), - glob: tool({ - description: "Stub glob tool", - inputSchema: z.object({ - pattern: z.string(), - path: z.string().optional(), + glob: tool({ + description: "Stub glob tool", + inputSchema: z.object({ + pattern: z.string(), + path: z.string().optional(), + }), + execute: async () => ({ output: "stub" }), }), - execute: async () => ({ output: "stub" }), - }), + }, }, - }) + ctx, + ) const capture = await request const body = capture.body @@ -1244,10 +1264,10 @@ describe("session.llm.stream", () => { }, }) - await WithInstance.provide({ + await withTestInstance({ directory: tmp.path, - fn: async () => { - const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id)) + fn: async (ctx) => { + const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id), ctx) const sessionID = SessionID.make("session-test-4") const agent = { name: "test", @@ -1267,15 +1287,18 @@ describe("session.llm.stream", () => { model: { providerID: ProviderID.make(providerID), modelID: resolved.id }, } satisfies MessageV2.User - await drain({ - user, - sessionID, - model: resolved, - agent, - system: ["You are a helpful assistant."], - messages: [{ role: "user", content: "Hello" }], - tools: {}, - }) + await drain( + { + user, + sessionID, + model: resolved, + agent, + system: ["You are a helpful assistant."], + messages: [{ role: "user", content: "Hello" }], + tools: {}, + }, + ctx, + ) const capture = await request const body = capture.body diff --git a/packages/opencode/test/session/network.test.ts b/packages/opencode/test/session/network.test.ts index 2ddf1bb053..f25d7e97d8 100644 --- a/packages/opencode/test/session/network.test.ts +++ b/packages/opencode/test/session/network.test.ts @@ -1,8 +1,7 @@ // kilocode_change - new file import { afterEach, describe, expect, test } from "bun:test" import { Bus } from "../../src/bus" -import { WithInstance } from "../../src/project/with-instance" -import { tmpdir } from "../fixture/fixture" +import { provideTestInstance, tmpdir } from "../fixture/fixture" import { SessionNetwork } from "../../src/session/network" import { SessionID } from "../../src/session/schema" @@ -88,7 +87,7 @@ describe("session.network", () => { test("reply resolves pending request", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const { promise } = await SessionNetwork.ask({ @@ -107,7 +106,7 @@ describe("session.network", () => { test("restore auto-resumes pending request after cancellation window", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const run = manual() @@ -130,7 +129,7 @@ describe("session.network", () => { test("reject wins before restored auto-resume fires", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const run = manual() @@ -151,7 +150,7 @@ describe("session.network", () => { test("abort cancels restored auto-resume timer", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const run = manual() @@ -173,7 +172,7 @@ describe("session.network", () => { test("reject rejects pending request", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const { promise } = await SessionNetwork.ask({ @@ -192,7 +191,7 @@ describe("session.network", () => { test("aborted signal rejects without publishing asked", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const abort = new AbortController() @@ -223,7 +222,7 @@ describe("session.network", () => { test("abort during pending ask rejects with AbortError and cleans up", async () => { await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ + await provideTestInstance({ directory: tmp.path, fn: async () => { const abort = new AbortController() diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index 0f057b6d13..2518ff9348 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -27,6 +27,7 @@ import { testEffect } from "../lib/effect" import { raw, reply, TestLLMServer } from "../lib/llm-server" import { SyncEvent } from "@/sync" import { RuntimeFlags } from "@/effect/runtime-flags" +import { EventV2Bridge } from "@/event-v2-bridge" void Log.init({ print: false }) @@ -181,6 +182,7 @@ const deps = Layer.mergeAll( Provider.defaultLayer, status, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, ).pipe(Layer.provideMerge(infra)) const env = Layer.mergeAll( TestLLMServer.layer, @@ -193,6 +195,19 @@ const env = Layer.mergeAll( ) const it = testEffect(env) +// kilocode_change start - exercise non-default output token ceilings in the processor +const capped = testEffect( + Layer.mergeAll( + TestLLMServer.layer, + SessionProcessor.layer.pipe( + Layer.provide(summary), + Layer.provide(Image.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true, outputTokenMax: 8_000 })), + Layer.provideMerge(deps), + ), + ), +) +// kilocode_change end const boot = Effect.fn("test.boot")(function* () { const processors = yield* SessionProcessor.Service @@ -380,6 +395,48 @@ it.live("session.processor effect tests stop after token overflow requests compa ), ) +// kilocode_change start - configured output ceiling must reach finish-step overflow accounting +capped.live("session.processor respects the configured output token ceiling", () => + provideTmpdirServer( + ({ dir, llm }) => + Effect.gen(function* () { + const { processors, session, provider } = yield* boot() + yield* llm.text("within capacity", { usage: { input: 91_000, output: 0 } }) + + const chat = yield* session.create({}) + const parent = yield* user(chat.id, "stay within the configured capacity") + const msg = yield* assistant(chat.id, parent.id, path.resolve(dir)) + const mdl = yield* provider.getModel(ref.providerID, ref.modelID) + const handle = yield* processors.create({ + assistantMessage: msg, + sessionID: chat.id, + model: mdl, + }) + + const value = yield* handle.process({ + user: { + id: parent.id, + sessionID: chat.id, + role: "user", + time: parent.time, + agent: parent.agent, + model: { providerID: ref.providerID, modelID: ref.modelID }, + } satisfies MessageV2.User, + sessionID: chat.id, + model: mdl, + agent: agent(), + system: [], + messages: [{ role: "user", content: "stay within the configured capacity" }], + tools: {}, + }) + + expect(value).toBe("continue") + }), + { git: true, config: (url) => providerCfg(url) }, + ), +) +// kilocode_change end + it.live("session.processor effect tests capture reasoning from http mock", () => provideTmpdirServer( ({ dir, llm }) => diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 82836741ce..f3b7ff44c6 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -58,6 +58,7 @@ import { reply, TestLLMServer } from "../lib/llm-server" import { SyncEvent } from "@/sync" import { RuntimeFlags } from "@/effect/runtime-flags" import { BackgroundJob } from "@/background/job" +import { EventV2Bridge } from "@/event-v2-bridge" void Log.init({ print: false }) const summary = Layer.succeed( @@ -196,6 +197,7 @@ function makeHttp() { AppFileSystem.defaultLayer, status, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, ).pipe(Layer.provideMerge(infra), Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true }))) const question = Question.layer.pipe(Layer.provideMerge(deps)) const todo = Todo.layer.pipe(Layer.provideMerge(deps)) diff --git a/packages/opencode/test/session/shell-v2.test.ts b/packages/opencode/test/session/shell-v2.test.ts index 294b2667b0..b497f3e79f 100644 --- a/packages/opencode/test/session/shell-v2.test.ts +++ b/packages/opencode/test/session/shell-v2.test.ts @@ -2,9 +2,9 @@ import { describe, expect, test } from "bun:test" import * as DateTime from "effect/DateTime" import { SessionID } from "../../src/session/schema" -import { EventV2 } from "../../src/v2/event" -import { SessionEvent } from "../../src/v2/session-event" -import { SessionMessageUpdater } from "../../src/v2/session-message-updater" +import { EventV2 } from "@opencode-ai/core/event" +import { SessionEvent } from "@opencode-ai/core/session-event" +import { SessionMessageUpdater } from "@opencode-ai/core/session-message-updater" describe("v2 shell event correlation", () => { test("an unmatched end is ignored before a matching start and end complete one record", () => { diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index e4476a20e9..b87c6f4e2e 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -61,6 +61,7 @@ import { Format } from "../../src/format" import { Reference } from "../../src/reference/reference" import { SyncEvent } from "@/sync" import { RuntimeFlags } from "@/effect/runtime-flags" +import { EventV2Bridge } from "@/event-v2-bridge" void Log.init({ print: false }) @@ -129,6 +130,7 @@ function makeHttp() { BackgroundJob.defaultLayer, status, SyncEvent.defaultLayer, + EventV2Bridge.defaultLayer, ).pipe(Layer.provideMerge(infra)) const question = Question.layer.pipe(Layer.provideMerge(deps)) const todo = Todo.layer.pipe(Layer.provideMerge(deps)) diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 4bcf8d54e5..a219208233 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -15,18 +15,19 @@ import fs from "fs/promises" const node = CrossSpawnSpawner.defaultLayer -const skills = (disableClaudeCodeSkills: boolean) => +const skills = (disableExternalSkills: boolean, disableClaudeCodeSkills: boolean) => Skill.layer.pipe( Layer.provide(Discovery.defaultLayer), Layer.provide(Config.defaultLayer), Layer.provide(Bus.layer), Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Global.layer), - Layer.provide(RuntimeFlags.layer({ disableClaudeCodeSkills })), + Layer.provide(RuntimeFlags.layer({ disableExternalSkills, disableClaudeCodeSkills })), ) -const it = testEffect(Layer.mergeAll(skills(false), node)) -const itWithoutClaudeCodeSkills = testEffect(Layer.mergeAll(skills(true), node)) +const it = testEffect(Layer.mergeAll(skills(false, false), node)) +const itWithoutExternalSkills = testEffect(Layer.mergeAll(skills(true, false), node)) +const itWithoutClaudeCodeSkills = testEffect(Layer.mergeAll(skills(false, true), node)) // kilocode_change async function createGlobalSkill(homeDir: string) { const skillDir = path.join(homeDir, ".claude", "skills", "global-test-skill") @@ -425,6 +426,53 @@ description: A skill in the .agents/skills directory. ), ) + itWithoutExternalSkills.live("skips external skill directories when disabled", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + yield* Effect.promise(() => + Promise.all([ + Bun.write( + path.join(dir, ".claude", "skills", "claude-skill", "SKILL.md"), + `--- +name: claude-skill +description: A skill in the .claude/skills directory. +--- + +# Claude Skill +`, + ), + Bun.write( + path.join(dir, ".agents", "skills", "agent-skill", "SKILL.md"), + `--- +name: agent-skill +description: A skill in the .agents/skills directory. +--- + +# Agent Skill +`, + ), + Bun.write( + path.join(dir, ".opencode", "skill", "opencode-skill", "SKILL.md"), + `--- +name: opencode-skill +description: A skill in the .opencode/skill directory. +--- + +# OpenCode Skill +`, + ), + ]), + ) + + const skill = yield* Skill.Service + const list = discovered(yield* skill.all()) // kilocode_change + expect(list.map((s) => s.name)).toEqual(["opencode-skill"]) + }), + { git: true }, + ), + ) + it.live("properly resolves directories that skills live in", () => provideTmpdirInstance( (dir) => diff --git a/packages/opencode/test/storage/db.test.ts b/packages/opencode/test/storage/db.test.ts index d56c0bd1ec..0004310aa1 100644 --- a/packages/opencode/test/storage/db.test.ts +++ b/packages/opencode/test/storage/db.test.ts @@ -26,4 +26,13 @@ describe("Database.getChannelPath", () => { expect(Database.getChannelPath(flags)).toBe(path.join(Global.Path.data, "kilo.db")) }).pipe(Effect.provide(RuntimeFlags.layer({ disableChannelDb: true }))), ) + + it.effect("accepts RuntimeFlags with skipMigrations for database callers", () => + Effect.gen(function* () { + const flags = yield* RuntimeFlags.Service + + expect(flags.skipMigrations).toBe(true) + expect(Database.getChannelPath(flags)).toBe(Database.getChannelPath({ disableChannelDb: flags.disableChannelDb })) + }).pipe(Effect.provide(RuntimeFlags.layer({ skipMigrations: true }))), + ) }) diff --git a/packages/opencode/test/sync/index.test.ts b/packages/opencode/test/sync/index.test.ts index c4e5b86062..2fbfc9b94c 100644 --- a/packages/opencode/test/sync/index.test.ts +++ b/packages/opencode/test/sync/index.test.ts @@ -13,7 +13,10 @@ import { RuntimeFlags } from "@/effect/runtime-flags" const it = testEffect( Layer.mergeAll( - SyncEvent.layer.pipe(Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: true }))), + SyncEvent.layer.pipe( + Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: true })), + Layer.provideMerge(Bus.layer), + ), CrossSpawnSpawner.defaultLayer, ), ) @@ -114,7 +117,8 @@ describe("SyncEvent", () => { const received = new Promise((done) => { resolve = done }) - const dispose = Bus.subscribeAll((event) => { + const bus = yield* Bus.Service + const dispose = yield* bus.subscribeAllCallback((event) => { events.push(event) resolve() }) diff --git a/packages/opencode/test/tool/external-directory.test.ts b/packages/opencode/test/tool/external-directory.test.ts index 2dcb157c42..b5d96ee50e 100644 --- a/packages/opencode/test/tool/external-directory.test.ts +++ b/packages/opencode/test/tool/external-directory.test.ts @@ -48,7 +48,7 @@ describe("tool.assertExternalDirectory", () => { }), ) - it.live("no-ops for paths inside Instance.directory", () => + it.live("no-ops for paths inside the instance directory", () => provideInstance("/tmp/project")( Effect.gen(function* () { const { requests, ctx } = makeCtx() diff --git a/packages/opencode/test/tool/lsp.test.ts b/packages/opencode/test/tool/lsp.test.ts index 875af8e010..875edc1c05 100644 --- a/packages/opencode/test/tool/lsp.test.ts +++ b/packages/opencode/test/tool/lsp.test.ts @@ -6,7 +6,6 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "@/lsp/lsp" import { Permission } from "../../src/permission" -import { Instance } from "../../src/project/instance" import { MessageID, SessionID } from "../../src/session/schema" import { Tool } from "@/tool/tool" import { Truncate } from "@/tool/truncate" diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 0b695a09d8..33cbb771f6 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect } from "bun:test" -import { Cause, Effect, Exit, Layer } from "effect" +import { Cause, Effect, Exit, Layer, Stream } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" @@ -10,7 +10,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags" import { Git } from "@/git" import { LSP } from "@/lsp/lsp" import { Permission } from "../../src/permission" -import { Instance } from "../../src/project/instance" import { SessionID, MessageID } from "../../src/session/schema" import { Instruction } from "../../src/session/instruction" import { ReadTool } from "../../src/tool/read" @@ -367,6 +366,38 @@ describe("tool.read truncation", () => { }), ) + it.instance("stops streaming after the byte cap", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "huge.txt") + const content = `${"x".repeat(80)}\n`.repeat(50_000) + yield* put(filepath, content) + + const fs = yield* AppFileSystem.Service + const counter = { bytes: 0 } + const result = yield* run({ filePath: filepath }).pipe( + Effect.provideService( + AppFileSystem.Service, + AppFileSystem.Service.of({ + ...fs, + stream: (file, options) => + fs.stream(file, options).pipe( + Stream.tap((chunk) => + Effect.sync(() => { + counter.bytes += chunk.length + }), + ), + ), + }), + ), + ) + + expect(result.metadata.truncated).toBe(true) + expect(result.output).toContain("Output capped at") + expect(counter.bytes).toBeLessThan(Buffer.byteLength(content, "utf-8") / 2) + }), + ) + it.instance("truncates by line count when limit is specified", () => Effect.gen(function* () { const test = yield* TestInstance diff --git a/packages/opencode/test/tool/recall.test.ts b/packages/opencode/test/tool/recall.test.ts index 50c08882e9..f338be9f9e 100644 --- a/packages/opencode/test/tool/recall.test.ts +++ b/packages/opencode/test/tool/recall.test.ts @@ -4,11 +4,10 @@ import { $ } from "bun" import { Effect } from "effect" import { Session } from "../../src/session/session" import path from "path" -import { WithInstance } from "../../src/project/with-instance" import { RecallTool } from "../../src/tool/recall" import { AppRuntime } from "../../src/effect/app-runtime" import { resetDatabase } from "../fixture/db" -import { tmpdir } from "../fixture/fixture" +import { provideTestInstance, tmpdir } from "../fixture/fixture" import type { Tool } from "../../src/tool/tool" import { SessionID, MessageID } from "../../src/session/schema" import { RemoteSender } from "../../src/kilo-sessions/remote-sender" @@ -33,8 +32,7 @@ afterEach(async () => { await resetDatabase() }) -const create = (title: string) => - Effect.runPromise(Session.Service.use((svc) => svc.create({ title })).pipe(Effect.provide(Session.defaultLayer))) +const create = (title: string) => AppRuntime.runPromise(Session.Service.use((svc) => svc.create({ title }))) describe("tool.recall", () => { test("search is limited to the current project worktrees", async () => { @@ -47,20 +45,20 @@ describe("tool.recall", () => { await Bun.write(path.join(first.path, ".git", "opencode"), "stale-project-id") try { - await WithInstance.provide({ + await provideTestInstance({ directory: first.path, fn: () => create("search-target root"), }) - await WithInstance.provide({ + await provideTestInstance({ directory: worktree, fn: () => create("search-target worktree"), }) - await WithInstance.provide({ + await provideTestInstance({ directory: second.path, fn: () => create("search-target other"), }) - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: first.path, fn: async () => { const info = await AppRuntime.runPromise(RecallTool) @@ -85,12 +83,12 @@ describe("tool.recall", () => { await using second = await tmpdir({ git: true }) try { - const session = await WithInstance.provide({ + const session = await provideTestInstance({ directory: second.path, fn: () => create("other-project-session"), }) - const err = await WithInstance.provide({ + const err = await provideTestInstance({ directory: first.path, fn: async () => { const info = await AppRuntime.runPromise(RecallTool) @@ -117,12 +115,12 @@ describe("tool.recall", () => { await Bun.write(path.join(first.path, ".git", "opencode"), "stale-project-id") try { - const session = await WithInstance.provide({ + const session = await provideTestInstance({ directory: worktree, fn: () => create("worktree readable"), }) - const result = await WithInstance.provide({ + const result = await provideTestInstance({ directory: first.path, fn: async () => { const info = await AppRuntime.runPromise(RecallTool) diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 9716400fd5..518399e8bb 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect } from "bun:test" import path from "path" import fs from "fs/promises" -import { pathToFileURL } from "url" +import { fileURLToPath, pathToFileURL } from "url" import { Effect, Layer, Result, Schema } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { ToolRegistry } from "@/tool/registry" @@ -171,6 +171,33 @@ describe("tool.registry", () => { }), ) + it.instance("ignores non-tool exports in .opencode/tool files", () => + Effect.gen(function* () { + const test = yield* TestInstance + const tool = path.join(test.directory, ".opencode", "tool") + yield* Effect.promise(() => fs.mkdir(tool, { recursive: true })) + yield* Effect.promise(() => + Bun.write( + path.join(tool, "mixed.ts"), + [ + "export const helper = 'not a tool'", + "export default {", + " description: 'mixed tool',", + " args: {},", + " execute: async () => 'ok',", + "}", + "", + ].join("\n"), + ), + ) + + const registry = yield* ToolRegistry.Service + const ids = yield* registry.ids() + expect(ids).toContain("mixed") + expect(ids).not.toContain("mixed_helper") + }), + ) + it.instance("loads tools from .opencode/tools (plural)", () => Effect.gen(function* () { const test = yield* TestInstance @@ -249,6 +276,73 @@ describe("tool.registry", () => { }), ) + it.instance( + "preserves Zod arg descriptions from older config-scoped plugin packages", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const opencode = path.join(test.directory, ".opencode") + const customTools = path.join(opencode, "tools") + const plugin = path.join(opencode, "node_modules", "@kilocode", "plugin") // kilocode_change + yield* Effect.promise(() => fs.mkdir(path.join(plugin, "dist"), { recursive: true })) + yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true })) + yield* Effect.promise(() => + fs.cp(path.dirname(fileURLToPath(import.meta.resolve("zod"))), path.join(opencode, "node_modules", "zod"), { + dereference: true, + recursive: true, + }), + ) + yield* Effect.promise(() => + Bun.write( + path.join(plugin, "package.json"), + JSON.stringify({ name: "@kilocode/plugin", type: "module", exports: { ".": "./dist/index.js" } }), // kilocode_change + ), + ) + yield* Effect.promise(() => + Bun.write( + path.join(plugin, "dist", "index.js"), + [ + "import { z } from 'zod'", + "export function tool(input) {", + " return input", + "}", + "tool.schema = z", + "", + ].join("\n"), + ), + ) + yield* Effect.promise(() => + Bun.write( + path.join(customTools, "addition.ts"), + [ + 'import { tool } from "@kilocode/plugin"', // kilocode_change + "export default tool({", + " description: 'Use this tool to add two numbers and return their sum.',", + " args: {", + " left: tool.schema.number().describe('The first number to add'),", + " right: tool.schema.number().describe('The second number to add'),", + " },", + " execute: async (args) => `${args.left} + ${args.right} = ${args.left + args.right}`,", + "})", + "", + ].join("\n"), + ), + ) + + const registry = yield* ToolRegistry.Service + const loaded = (yield* registry.all()).find((tool) => tool.id === "addition") + if (!loaded) throw new Error("custom addition tool was not loaded") + + expect(ToolJsonSchema.fromTool(loaded)).toMatchObject({ + properties: { + left: { type: "number", description: "The first number to add" }, + right: { type: "number", description: "The second number to add" }, + }, + }) + }), + 20_000, + ) + it.instance("preserves attachments from structured custom tool results", () => Effect.gen(function* () { const test = yield* TestInstance diff --git a/packages/opencode/test/tool/repo_overview.test.ts b/packages/opencode/test/tool/repo_overview.test.ts index 556fa05d1f..c854e51a3f 100644 --- a/packages/opencode/test/tool/repo_overview.test.ts +++ b/packages/opencode/test/tool/repo_overview.test.ts @@ -97,6 +97,21 @@ describe("tool.repo_overview", () => { ), ) + it.live("resolves relative paths from the instance directory", () => + provideTmpdirInstance((dir) => + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(path.join(dir, "nested", "README.md"), "# Nested\n") + + const tool = yield* init() + const result = yield* tool.execute({ path: "nested" }, ctx) + + expect(result.metadata.path).toBe(path.join(dir, "nested")) + expect(result.output).toContain("README.md") + }), + ), + ) + it.live("resolves a cached repository from repository shorthand", () => provideTmpdirInstance((_dir) => Effect.gen(function* () { diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index 7c742639e0..35a3fa60f6 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -5,7 +5,6 @@ import path from "path" import { pathToFileURL } from "url" import type { Permission } from "../../src/permission" import type { Tool } from "@/tool/tool" -import { Instance } from "../../src/project/instance" import { SkillTool } from "../../src/tool/skill" import { ToolRegistry } from "@/tool/registry" import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture" diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index f6ac57a8ce..8a5545e717 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -3,7 +3,6 @@ import { Effect, Layer } from "effect" import path from "path" import fs from "fs/promises" import { WriteTool } from "../../src/tool/write" -import { Instance } from "../../src/project/instance" import { LSP } from "@/lsp/lsp" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Bus } from "../../src/bus" diff --git a/packages/opencode/test/v2/session-message-updater.test.ts b/packages/opencode/test/v2/session-message-updater.test.ts index 180483937c..588521281c 100644 --- a/packages/opencode/test/v2/session-message-updater.test.ts +++ b/packages/opencode/test/v2/session-message-updater.test.ts @@ -1,11 +1,11 @@ import { expect, test } from "bun:test" import * as DateTime from "effect/DateTime" import { SessionID } from "../../src/session/schema" -import { EventV2 } from "../../src/v2/event" +import { EventV2 } from "@opencode-ai/core/event" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" -import { SessionEvent } from "../../src/v2/session-event" -import { SessionMessageUpdater } from "../../src/v2/session-message-updater" +import { SessionEvent } from "@opencode-ai/core/session-event" +import { SessionMessageUpdater } from "@opencode-ai/core/session-message-updater" test("step snapshots carry over to assistant messages", () => { const state: SessionMessageUpdater.MemoryState = { messages: [] } diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 9dd15ad506..5fdb8014af 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -22,9 +22,9 @@ "zod": "catalog:" }, "peerDependencies": { - "@opentui/core": ">=0.2.10", - "@opentui/solid": ">=0.2.10", - "@opentui/keymap": ">=0.2.10" + "@opentui/core": ">=0.2.11", + "@opentui/solid": ">=0.2.11", + "@opentui/keymap": ">=0.2.11" }, "peerDependenciesMeta": { "@opentui/core": { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 3e000b1d99..0a1652757f 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -5046,14 +5046,14 @@ export class Model extends HeyApiClient { */ public list( parameters?: { - instance?: { + location?: { directory?: string workspace?: string } }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }]) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) return (options?.client ?? this.client).get({ url: "/api/model", ...options, @@ -5070,14 +5070,14 @@ export class Provider2 extends HeyApiClient { */ public list( parameters?: { - instance?: { + location?: { directory?: string workspace?: string } }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }]) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) return (options?.client ?? this.client).get({ url: "/api/provider", ...options, @@ -5093,7 +5093,7 @@ export class Provider2 extends HeyApiClient { public get( parameters: { providerID: string - instance?: { + location?: { directory?: string workspace?: string } @@ -5106,7 +5106,7 @@ export class Provider2 extends HeyApiClient { { args: [ { in: "path", key: "providerID" }, - { in: "query", key: "instance" }, + { in: "query", key: "location" }, ], }, ], @@ -7169,7 +7169,7 @@ export class Kilocode extends HeyApiClient { /** * Remove a skill * - * Remove a skill by deleting its directory from disk and clearing it from cache. + * Remove a skill by deleting its manifest from disk and clearing it from cache. */ public removeSkill( parameters?: { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index af264d90b7..aec8d6975c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -44,9 +44,9 @@ export type Event = | EventSuggestionShown | EventSuggestionAccepted | EventSuggestionDismissed - | EventSessionCompacted | EventCommandExecuted | EventProjectUpdated + | EventSessionCompacted | EventVcsBranchUpdated | EventKiloSessionsRemoteStatusChanged | EventWorkspaceReady @@ -60,6 +60,40 @@ export type Event = | EventPtyDeleted | EventInstallationUpdated | EventInstallationUpdateAvailable + | EventMessageUpdated + | EventMessageRemoved + | EventMessagePartUpdated + | EventMessagePartRemoved + | EventSessionCreated + | EventSessionUpdated + | EventSessionDeleted + | EventSessionNextAgentSwitched + | EventSessionNextModelSwitched + | EventSessionNextPrompted + | EventSessionNextSynthetic + | EventSessionNextShellStarted + | EventSessionNextShellEnded + | EventSessionNextStepStarted + | EventSessionNextStepEnded + | EventSessionNextStepFailed + | EventSessionNextTextStarted + | EventSessionNextTextDelta + | EventSessionNextTextEnded + | EventSessionNextReasoningStarted + | EventSessionNextReasoningDelta + | EventSessionNextReasoningEnded + | EventSessionNextToolInputStarted + | EventSessionNextToolInputDelta + | EventSessionNextToolInputEnded + | EventSessionNextToolCalled + | EventSessionNextToolProgress + | EventSessionNextToolSuccess + | EventSessionNextToolFailed + | EventSessionNextRetried + | EventSessionNextCompactionStarted + | EventSessionNextCompactionDelta + | EventSessionNextCompactionEnded + | EventCatalogModelUpdated export type OAuth = { type: "oauth" @@ -904,9 +938,9 @@ export type GlobalEvent = { | EventSuggestionShown | EventSuggestionAccepted | EventSuggestionDismissed - | EventSessionCompacted | EventCommandExecuted | EventProjectUpdated + | EventSessionCompacted | EventVcsBranchUpdated | EventKiloSessionsRemoteStatusChanged | EventWorkspaceReady @@ -920,6 +954,40 @@ export type GlobalEvent = { | EventPtyDeleted | EventInstallationUpdated | EventInstallationUpdateAvailable + | EventMessageUpdated + | EventMessageRemoved + | EventMessagePartUpdated + | EventMessagePartRemoved + | EventSessionCreated + | EventSessionUpdated + | EventSessionDeleted + | EventSessionNextAgentSwitched + | EventSessionNextModelSwitched + | EventSessionNextPrompted + | EventSessionNextSynthetic + | EventSessionNextShellStarted + | EventSessionNextShellEnded + | EventSessionNextStepStarted + | EventSessionNextStepEnded + | EventSessionNextStepFailed + | EventSessionNextTextStarted + | EventSessionNextTextDelta + | EventSessionNextTextEnded + | EventSessionNextReasoningStarted + | EventSessionNextReasoningDelta + | EventSessionNextReasoningEnded + | EventSessionNextToolInputStarted + | EventSessionNextToolInputDelta + | EventSessionNextToolInputEnded + | EventSessionNextToolCalled + | EventSessionNextToolProgress + | EventSessionNextToolSuccess + | EventSessionNextToolFailed + | EventSessionNextRetried + | EventSessionNextCompactionStarted + | EventSessionNextCompactionDelta + | EventSessionNextCompactionEnded + | EventCatalogModelUpdated | SyncEventMessageUpdated | SyncEventMessageRemoved | SyncEventMessagePartUpdated @@ -1332,6 +1400,9 @@ export type Config = { small_model?: string subagent_model?: string subagent_variant?: string + subagent_variant_overrides?: { + [key: string]: string + } default_agent?: string username?: string mode?: { @@ -3065,14 +3136,6 @@ export type EventSuggestionDismissed = { } } -export type EventSessionCompacted = { - id: string - type: "session.compacted" - properties: { - sessionID: string - } -} - export type EventCommandExecuted = { id: string type: "command.executed" @@ -3090,6 +3153,14 @@ export type EventProjectUpdated = { properties: Project } +export type EventSessionCompacted = { + id: string + type: "session.compacted" + properties: { + sessionID: string + } +} + export type EventVcsBranchUpdated = { id: string type: "vcs.branch.updated" @@ -3198,6 +3269,95 @@ export type EventInstallationUpdateAvailable = { } } +export type EventMessageUpdated = { + id: string + type: "message.updated" + properties: { + sessionID: string + info: Message + } +} + +export type EventMessageRemoved = { + id: string + type: "message.removed" + properties: { + sessionID: string + messageID: string + } +} + +export type EventMessagePartUpdated = { + id: string + type: "message.part.updated" + properties: { + sessionID: string + part: Part + time: number + } +} + +export type EventMessagePartRemoved = { + id: string + type: "message.part.removed" + properties: { + sessionID: string + messageID: string + partID: string + } +} + +export type EventSessionCreated = { + id: string + type: "session.created" + properties: { + sessionID: string + info: Session + } +} + +export type EventSessionUpdated = { + id: string + type: "session.updated" + properties: { + sessionID: string + info: Session + } +} + +export type EventSessionDeleted = { + id: string + type: "session.deleted" + properties: { + sessionID: string + info: Session + } +} + +export type EventSessionNextAgentSwitched = { + id: string + type: "session.next.agent.switched" + properties: { + timestamp: number + sessionID: string + agent: string + } +} + +export type EventSessionNextModelSwitched = { + id: string + type: "session.next.model.switched" + properties: { + timestamp: number + sessionID: string + model: { + id: string + providerID: string + variant: string + } + } +} + export type PromptSource = { start: number end: number @@ -3229,131 +3389,533 @@ export type PromptReferenceAttachment = { source?: PromptSource } -export type SessionErrorUnknown = { - type: "unknown" - message: string +export type EventSessionNextPrompted = { + id: string + type: "session.next.prompted" + properties: { + timestamp: number + sessionID: string + prompt: Prompt + } } -export type ToolTextContent = { - type: "text" - text: string +export type EventSessionNextSynthetic = { + id: string + type: "session.next.synthetic" + properties: { + timestamp: number + sessionID: string + text: string + } } -export type ToolFileContent = { - type: "file" - uri: string - mime: string - name?: string +export type EventSessionNextShellStarted = { + id: string + type: "session.next.shell.started" + properties: { + timestamp: number + sessionID: string + callID: string + command: string + } } -export type SessionNextRetryError = { - message: string - statusCode?: number - isRetryable: boolean - responseHeaders?: { - [key: string]: string - } - responseBody?: string - metadata?: { - [key: string]: string +export type EventSessionNextShellEnded = { + id: string + type: "session.next.shell.ended" + properties: { + timestamp: number + sessionID: string + callID: string + output: string } } -export type SessionInfo = { +export type EventSessionNextStepStarted = { id: string - parentID?: string - projectID: string - workspaceID?: string - path?: string - agent?: string - model?: { - id: string - providerID: string - variant: string - } - cost: number - tokens: { - input: number - output: number - reasoning: number - cache: { - read: number - write: number + type: "session.next.step.started" + properties: { + timestamp: number + sessionID: string + agent: string + model: { + id: string + providerID: string + variant: string } + snapshot?: string } - time: { - created: number - updated: number - archived?: number +} + +export type EventSessionNextStepEnded = { + id: string + type: "session.next.step.ended" + properties: { + timestamp: number + sessionID: string + finish: string + cost: number + tokens: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } + snapshot?: string } - title: string } -export type SessionDelivery = "immediate" | "deferred" +export type SessionErrorUnknown = { + type: "unknown" + message: string +} -export type SessionMessageAgentSwitched = { +export type EventSessionNextStepFailed = { id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number + type: "session.next.step.failed" + properties: { + timestamp: number + sessionID: string + error: SessionErrorUnknown } - type: "agent-switched" - agent: string } -export type SessionMessageModelSwitched = { +export type EventSessionNextTextStarted = { id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - } - type: "model-switched" - model: { - id: string - providerID: string - variant: string + type: "session.next.text.started" + properties: { + timestamp: number + sessionID: string } } -export type SessionMessageUser = { +export type EventSessionNextTextDelta = { id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number + type: "session.next.text.delta" + properties: { + timestamp: number + sessionID: string + delta: string } - text: string - files?: Array - agents?: Array - references?: Array - type: "user" } -export type SessionMessageSynthetic = { +export type EventSessionNextTextEnded = { id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number + type: "session.next.text.ended" + properties: { + timestamp: number + sessionID: string + text: string } - sessionID: string - text: string - type: "synthetic" } -export type SessionMessageShell = { +export type EventSessionNextReasoningStarted = { id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number + type: "session.next.reasoning.started" + properties: { + timestamp: number + sessionID: string + reasoningID: string + } +} + +export type EventSessionNextReasoningDelta = { + id: string + type: "session.next.reasoning.delta" + properties: { + timestamp: number + sessionID: string + reasoningID: string + delta: string + } +} + +export type EventSessionNextReasoningEnded = { + id: string + type: "session.next.reasoning.ended" + properties: { + timestamp: number + sessionID: string + reasoningID: string + text: string + } +} + +export type EventSessionNextToolInputStarted = { + id: string + type: "session.next.tool.input.started" + properties: { + timestamp: number + sessionID: string + callID: string + name: string + } +} + +export type EventSessionNextToolInputDelta = { + id: string + type: "session.next.tool.input.delta" + properties: { + timestamp: number + sessionID: string + callID: string + delta: string + } +} + +export type EventSessionNextToolInputEnded = { + id: string + type: "session.next.tool.input.ended" + properties: { + timestamp: number + sessionID: string + callID: string + text: string + } +} + +export type EventSessionNextToolCalled = { + id: string + type: "session.next.tool.called" + properties: { + timestamp: number + sessionID: string + callID: string + tool: string + input: { + [key: string]: unknown + } + provider: { + executed: boolean + metadata?: { + [key: string]: unknown + } + } + } +} + +export type ToolTextContent = { + type: "text" + text: string +} + +export type ToolFileContent = { + type: "file" + uri: string + mime: string + name?: string +} + +export type EventSessionNextToolProgress = { + id: string + type: "session.next.tool.progress" + properties: { + timestamp: number + sessionID: string + callID: string + structured: { + [key: string]: unknown + } + content: Array + } +} + +export type EventSessionNextToolSuccess = { + id: string + type: "session.next.tool.success" + properties: { + timestamp: number + sessionID: string + callID: string + structured: { + [key: string]: unknown + } + content: Array + provider: { + executed: boolean + metadata?: { + [key: string]: unknown + } + } + } +} + +export type EventSessionNextToolFailed = { + id: string + type: "session.next.tool.failed" + properties: { + timestamp: number + sessionID: string + callID: string + error: SessionErrorUnknown + provider: { + executed: boolean + metadata?: { + [key: string]: unknown + } + } + } +} + +export type SessionNextRetryError = { + message: string + statusCode?: number + isRetryable: boolean + responseHeaders?: { + [key: string]: string + } + responseBody?: string + metadata?: { + [key: string]: string + } +} + +export type EventSessionNextRetried = { + id: string + type: "session.next.retried" + properties: { + timestamp: number + sessionID: string + attempt: number + error: SessionNextRetryError + } +} + +export type EventSessionNextCompactionStarted = { + id: string + type: "session.next.compaction.started" + properties: { + timestamp: number + sessionID: string + reason: "auto" | "manual" + } +} + +export type EventSessionNextCompactionDelta = { + id: string + type: "session.next.compaction.delta" + properties: { + timestamp: number + sessionID: string + text: string + } +} + +export type EventSessionNextCompactionEnded = { + id: string + type: "session.next.compaction.ended" + properties: { + timestamp: number + sessionID: string + text: string + include?: string + } +} + +export type ModelV2Info = { + id: string + apiID: string + providerID: string + family?: string + name: string + endpoint: + | { + type: "unknown" + } + | { + type: "openai/responses" + url: string + websocket?: boolean + } + | { + type: "openai/completions" + url: string + reasoning?: + | { + type: "reasoning_content" + } + | { + type: "reasoning_details" + } + } + | { + type: "anthropic/messages" + url: string + } + | { + type: "aisdk" + package: string + url?: string + } + capabilities: { + tools: boolean + input: Array + output: Array + } + options: { + headers: { + [key: string]: string + } + body: { + [key: string]: unknown + } + aisdk: { + provider: { + [key: string]: unknown + } + request: { + [key: string]: unknown + } + } + variant?: string + } + variants: Array<{ + id: string + headers: { + [key: string]: string + } + body: { + [key: string]: unknown + } + aisdk: { + provider: { + [key: string]: unknown + } + request: { + [key: string]: unknown + } + } + }> + time: { + released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" + } + cost: Array<{ + tier?: { + type: "context" + size: number + } + input: number + output: number + cache: { + read: number + write: number + } + }> + status: "alpha" | "beta" | "deprecated" | "active" + enabled: boolean + limit: { + context: number + input?: number + output: number + } +} + +export type EventCatalogModelUpdated = { + id: string + type: "catalog.model.updated" + properties: { + model: ModelV2Info + } +} + +export type SessionInfo = { + id: string + parentID?: string + projectID: string + workspaceID?: string + path?: string + agent?: string + model?: { + id: string + providerID: string + variant: string + } + cost: number + tokens: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } + time: { + created: number + updated: number + archived?: number + } + title: string +} + +export type SessionDelivery = "immediate" | "deferred" + +export type SessionMessageAgentSwitched = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + type: "agent-switched" + agent: string +} + +export type SessionMessageModelSwitched = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + type: "model-switched" + model: { + id: string + providerID: string + variant: string + } +} + +export type SessionMessageUser = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + text: string + files?: Array + agents?: Array + references?: Array + type: "user" +} + +export type SessionMessageSynthetic = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + sessionID: string + text: string + type: "synthetic" +} + +export type SessionMessageShell = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number completed?: number } type: "shell" @@ -3494,12 +4056,26 @@ export type SessionMessage = | SessionMessageAssistant | SessionMessageCompaction -export type ModelV2Info = { +export type ProviderV2Info = { id: string - apiID: string - providerID: string - family?: string name: string + enabled: + | false + | { + via: "env" + name: string + } + | { + via: "auth" + service: string + } + | { + via: "custom" + data: { + [key: string]: unknown + } + } + env: Array endpoint: | { type: "unknown" @@ -3529,11 +4105,6 @@ export type ModelV2Info = { package: string url?: string } - capabilities: { - tools: boolean - input: Array - output: Array - } options: { headers: { [key: string]: string @@ -3549,69 +4120,26 @@ export type ModelV2Info = { [key: string]: unknown } } - variant?: string - } - variants: Array<{ - id: string - headers: { - [key: string]: string - } - body: { - [key: string]: unknown - } - aisdk: { - provider: { - [key: string]: unknown - } - request: { - [key: string]: unknown - } - } - }> - time: { - released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" } - cost: Array<{ - tier?: { - type: "context" - size: number - } - input: number - output: number - cache: { - read: number - write: number - } - }> - status: "alpha" | "beta" | "deprecated" | "active" - enabled: boolean - limit: { - context: number - input?: number - output: number +} + +export type EventTuiToastShow1 = { + id: string + type: "tui.toast.show" + properties: { + title?: string + message: string + variant: "info" | "success" | "warning" | "error" + duration?: number } } -export type ProviderV2Info = { +export type ModelV2Info1 = { id: string + apiID: string + providerID: string + family?: string name: string - enabled: - | false - | { - via: "env" - name: string - } - | { - via: "auth" - service: string - } - | { - via: "custom" - data: { - [key: string]: unknown - } - } - env: Array endpoint: | { type: "unknown" @@ -3641,6 +4169,11 @@ export type ProviderV2Info = { package: string url?: string } + capabilities: { + tools: boolean + input: Array + output: Array + } options: { headers: { [key: string]: string @@ -3656,17 +4189,46 @@ export type ProviderV2Info = { [key: string]: unknown } } + variant?: string } -} - -export type EventTuiToastShow1 = { - id: string - type: "tui.toast.show" - properties: { - title?: string - message: string - variant: "info" | "success" | "warning" | "error" - duration?: number + variants: Array<{ + id: string + headers: { + [key: string]: string + } + body: { + [key: string]: unknown + } + aisdk: { + provider: { + [key: string]: unknown + } + request: { + [key: string]: unknown + } + } + }> + time: { + released: number | "NaN" | "Infinity" | "-Infinity" + } + cost: Array<{ + tier?: { + type: "context" + size: number + } + input: number + output: number + cache: { + read: number + write: number + } + }> + status: "alpha" | "beta" | "deprecated" | "active" + enabled: boolean + limit: { + context: number + input?: number + output: number } } @@ -7014,7 +7576,7 @@ export type V2ModelListData = { body?: never path?: never query?: { - instance?: { + location?: { directory?: string workspace?: string } @@ -7035,7 +7597,7 @@ export type V2ProviderListData = { body?: never path?: never query?: { - instance?: { + location?: { directory?: string workspace?: string } @@ -7058,7 +7620,7 @@ export type V2ProviderGetData = { providerID: string } query?: { - instance?: { + location?: { directory?: string workspace?: string } diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx index 813bc92410..675fef4f14 100644 --- a/packages/ui/src/components/markdown.tsx +++ b/packages/ui/src/components/markdown.tsx @@ -1,4 +1,5 @@ -import { useMarked, deferredHighlight, fnv1a } from "../context/marked" +import { useMarked } from "../context/marked" +import { deferredHighlight, fnv1a } from "../context/marked" // kilocode_change import { useI18n } from "../context/i18n" import DOMPurify from "dompurify" import morphdom from "morphdom" diff --git a/script/check-opencode-promise-facades.ts b/script/check-opencode-promise-facades.ts index 009bf05c2f..ea654c5e51 100644 --- a/script/check-opencode-promise-facades.ts +++ b/script/check-opencode-promise-facades.ts @@ -26,11 +26,10 @@ const allow: Record = { "cli/cmd/run/variant.shared.ts": "direct run variant persistence runtime boundary with test filesystem injection", "cli/cmd/tui/config/tui.ts": "separately tracked TUI config facade", "installation/index.ts": "existing installation facade outside #10655", - "session/compaction.ts": "existing compaction facade outside #10655", } const testAllow: Record = { - "control-plane/workspace.test.ts": { count: 3, reason: "existing runtime integration test" }, + "control-plane/workspace.test.ts": { count: 5, reason: "existing runtime integration test" }, "kilocode/config-resilience.test.ts": { count: 4, reason: "existing runtime integration test" }, "kilocode/config-validation.test.ts": { count: 2, reason: "existing runtime integration test" }, "kilocode/plan-followup.test.ts": { count: 4, reason: "existing runtime integration test" }, @@ -39,8 +38,10 @@ const testAllow: Record = { "kilocode/session/session.test.ts": { count: 5, reason: "existing runtime integration test" }, "provider/amazon-bedrock.test.ts": { count: 2, reason: "existing runtime integration test" }, "provider/provider.test.ts": { count: 3, reason: "existing runtime integration test" }, + "server/experimental-session-list.test.ts": { count: 2, reason: "Kilo session list integration test" }, + "server/httpapi-event.test.ts": { count: 6, reason: "event stream integration test" }, "session/llm.test.ts": { count: 2, reason: "existing runtime integration test" }, - "tool/recall.test.ts": { count: 10, reason: "existing runtime integration test" }, + "tool/recall.test.ts": { count: 11, reason: "existing runtime integration test" }, } const owned = (file: string) => file.startsWith("kilocode/") || file.startsWith("kilo-sessions/") diff --git a/script/upstream/utils/config.ts b/script/upstream/utils/config.ts index 7f7e9cea0c..0dbc81738e 100644 --- a/script/upstream/utils/config.ts +++ b/script/upstream/utils/config.ts @@ -127,8 +127,11 @@ export const defaultConfig: MergeConfig = { // czcode_change: czcode watches kilocode releases, not opencode directly ".github/workflows/watch-opencode-releases.yml", // Workflows deleted in Kilo (replaced or no longer needed) + ".github/workflows/close-prs.yml", ".github/workflows/opencode.yml", ".github/workflows/publish-vscode.yml", + // Upstream PR cleanup is replaced by .github/workflows/kilo-auto-close.yml + "script/github/close-prs.ts", // VS Code example configs (Kilo ships real .vscode/* files) ".vscode/launch.example.json", ".vscode/settings.example.json", @@ -169,6 +172,10 @@ export const defaultConfig: MergeConfig = { "github/bun.lock", "github/sst-env.d.ts", "github/.gitignore", + // czcode_change: VSCode visual-regression screenshots (Git LFS objects). czcode + // does not run VSCode visual regression tests, and these LFS blobs live only in + // upstream's LFS store — pulling them into czcode fails push with GH008. Skip them. + "packages/kilo-docs/public/img/screenshot-tests/**", ], // Files that should take upstream version and apply Kilo branding transforms diff --git a/upstream-merge-report-7.3.46.md b/upstream-merge-report-7.3.46.md new file mode 100644 index 0000000000..2c6df28822 --- /dev/null +++ b/upstream-merge-report-7.3.46.md @@ -0,0 +1,9044 @@ +# Upstream Merge Conflict Report + +Generated: 2026-06-18T07:43:48.560Z + +## Summary + +- **Upstream Version**: 7.3.46 +- **Upstream Commit**: `f388b60a` +- **Base Branch**: main +- **Merge Branch**: yunqiqiliang/kilo-opencode-v7.3.46 +- **Total Files Changed**: 4500 + +## Files by Recommendation + +### Skip (Auto-Remove) + +- `nix/desktop.nix` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/.gitignore` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/AGENTS.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/README.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/bunfig.toml` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/create-effect-simplification-spec.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/e2e/prompt/prompt-history.spec.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/e2e/prompt/prompt-shell.spec.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/e2e/prompt/prompt-slash-share.spec.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/e2e/session/session.spec.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/e2e/todo.spec.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/e2e/tsconfig.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/happydom.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/index.html` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/package.json` (package) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/playwright.config.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/_headers` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/apple-touch-icon-v3.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/apple-touch-icon.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/assets/JetBrainsMonoNerdFontMono-Regular.woff2` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/favicon-96x96-v3.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/favicon-96x96.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/favicon-v3.ico` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/favicon-v3.svg` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/favicon.ico` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/favicon.svg` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/oc-theme-preload.js` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/site.webmanifest` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/social-share-zen.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/social-share.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/web-app-manifest-192x192.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/public/web-app-manifest-512x512.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/addons/serialize.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/addons/serialize.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/app.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/debug-bar.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-connect-provider.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-custom-provider-form.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-custom-provider.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-custom-provider.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-edit-project.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-fork.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-manage-models.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-release-notes.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-select-directory.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-select-file.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-select-mcp.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-select-model-unpaid.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-select-model.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-select-provider.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-select-server.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/dialog-settings.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/file-tree.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/file-tree.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/link.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/model-tooltip.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/attachments.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/attachments.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/build-request-parts.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/build-request-parts.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/context-items.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/drag-overlay.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/editor-dom.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/editor-dom.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/files.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/history.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/history.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/image-attachments.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/paste.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/placeholder.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/placeholder.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/slash-popover.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/submit.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/prompt-input/submit.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/server/server-row.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session-context-usage.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/index.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-context-breakdown.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-context-breakdown.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-context-format.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-context-metrics.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-context-metrics.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-context-tab.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-header.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-new-view.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-sortable-tab.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/session/session-sortable-terminal-tab.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/settings-general.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/settings-keybinds.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/settings-list.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/settings-models.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/settings-providers.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/status-popover-body.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/status-popover.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/terminal.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/titlebar-history.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/titlebar-history.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/components/titlebar.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/constants/file-picker.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/command-keybind.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/command.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/command.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/comments.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/comments.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file-content-eviction-accounting.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file/content-cache.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file/path.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file/path.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file/tree-store.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file/types.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file/view-cache.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file/watcher.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/file/watcher.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sdk.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/bootstrap.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/child-store.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/child-store.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/event-reducer.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/event-reducer.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/eviction.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/queue.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/session-cache.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/session-cache.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/session-load.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/session-prefetch.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/session-prefetch.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/session-trim.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/session-trim.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/types.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/utils.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/global-sync/utils.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/highlights.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/language.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/layout-scroll.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/layout-scroll.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/layout.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/layout.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/local.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/model-variant.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/model-variant.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/models.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/notification.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/permission-auto-respond.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/permission-auto-respond.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/permission.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/platform.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/prompt.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/sdk.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/server.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/settings.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/sync-optimistic.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/sync.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/terminal-title.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/terminal.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/context/terminal.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/custom-elements.d.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/entry.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/env.d.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/hooks/use-providers.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/ar.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/br.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/bs.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/da.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/de.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/en.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/es.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/fr.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/ja.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/ko.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/nl.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/no.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/parity.test.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/pl.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/ru.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/th.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/tr.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/zh.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/i18n/zht.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/index.css` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/index.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/directory-layout.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/error.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/home.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout/deep-links.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout/helpers.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout/helpers.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout/inline-editor.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout/sidebar-items.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout/sidebar-project.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout/sidebar-shell.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/layout/sidebar-workspace.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/index.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-composer-region.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-composer-state.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-composer-state.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-followup-dock.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-permission-dock.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-question-dock.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-request-tree.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-revert-dock.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/composer/session-todo-dock.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/file-tab-scroll.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/file-tab-scroll.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/file-tabs.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/handoff.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/helpers.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/helpers.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/message-gesture.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/message-gesture.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/message-id-from-hash.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/message-timeline.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/review-tab.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/session-layout.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/session-model-helpers.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/session-model-helpers.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/session-side-panel.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/terminal-label.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/terminal-panel.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/terminal-panel.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/use-session-commands.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/use-session-hash-scroll.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/pages/session/use-session-hash-scroll.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/sst-env.d.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/theme-preload.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/agent.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/aim.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/base64.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/comment-note.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/diffs.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/diffs.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/id.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/notification-click.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/notification-click.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/persist.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/persist.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/prompt.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/prompt.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/runtime-adapters.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/runtime-adapters.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/same.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/scoped-cache.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/scoped-cache.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/server-errors.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/server-errors.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/server-health.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/server-health.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/server.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/session-title.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/solid-dnd.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/sound.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/terminal-writer.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/terminal-writer.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/time.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/uuid.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/uuid.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/worktree.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/src/utils/worktree.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/tsconfig.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/vite.config.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/app/vite.js` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/console/core/migrations/20260417071612_tidy_diamondback/snapshot.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/.gitignore` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/AGENTS.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/README.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/electron-builder.config.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/electron.vite.config.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/README.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/128x128.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/128x128@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/32x32.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/64x64.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square107x107Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square142x142Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square150x150Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square284x284Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square30x30Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square310x310Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square44x44Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square71x71Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/Square89x89Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/StoreLogo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/android/values/ic_launcher_background.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/dock.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/icon.icns` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/icon.ico` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/icon.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-20x20@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-20x20@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-29x29@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-29x29@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-40x40@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-40x40@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-512@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-60x60@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-60x60@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-76x76@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-76x76@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/beta/ios/AppIcon-83.5x83.5@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/128x128.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/128x128@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/32x32.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/64x64.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square107x107Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square142x142Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square150x150Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square284x284Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square30x30Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square310x310Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square44x44Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square71x71Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/Square89x89Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/StoreLogo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/android/values/ic_launcher_background.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/dock.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/icon.icns` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/icon.ico` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/icon.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-20x20@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-20x20@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-29x29@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-29x29@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-40x40@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-40x40@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-512@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-60x60@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-60x60@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-76x76@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-76x76@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/dev/ios/AppIcon-83.5x83.5@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/128x128.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/128x128@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/32x32.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/64x64.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square107x107Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square142x142Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square150x150Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square284x284Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square30x30Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square310x310Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square44x44Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square71x71Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/Square89x89Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/StoreLogo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/android/values/ic_launcher_background.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/dock.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/icon.icns` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/icon.ico` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/icon.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-20x20@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-20x20@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-29x29@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-29x29@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-40x40@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-40x40@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-512@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-60x60@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-60x60@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-76x76@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-76x76@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/icons/prod/ios/AppIcon-83.5x83.5@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/package.json` (package) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/resources/entitlements.plist` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/scripts/copy-bundles.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/scripts/copy-icons.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/scripts/finalize-latest-yml.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/scripts/prebuild.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/scripts/predev.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/scripts/prepare.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/scripts/utils.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/apps.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/constants.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/env.d.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/index.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/ipc.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/logging.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/markdown.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/menu.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/migrate.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/server.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/shell-env.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/shell-env.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/store.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/main/windows.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/preload/index.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/preload/types.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/cli.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/env.d.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/html.test.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/ar.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/br.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/bs.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/da.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/de.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/en.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/es.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/fr.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/index.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/ja.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/ko.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/nl.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/no.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/pl.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/ru.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/zh.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/i18n/zht.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/index.html` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/index.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/loading.html` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/loading.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/styles.css` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/updater.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/src/renderer/webview-zoom.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop-electron/tsconfig.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/.gitignore` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/README.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/index.html` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/package.json` (package) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/scripts/copy-bundles.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/scripts/finalize-latest-json.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/scripts/predev.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/scripts/prepare.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/scripts/utils.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/.gitignore` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/Cargo.lock` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/Cargo.toml` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/assets/nsis-header.bmp` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/assets/nsis-sidebar.bmp` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/build.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/capabilities/default.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/entitlements.plist` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/README.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/128x128.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/128x128@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/32x32.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/64x64.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square107x107Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square142x142Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square150x150Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square284x284Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square30x30Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square310x310Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square44x44Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square71x71Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/Square89x89Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/StoreLogo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-hdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-hdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-mdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-mdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/android/values/ic_launcher_background.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/icon.icns` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/icon.ico` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/icon.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-20x20@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-20x20@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-20x20@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-20x20@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-29x29@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-29x29@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-29x29@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-29x29@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-40x40@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-40x40@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-40x40@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-40x40@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-512@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-60x60@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-60x60@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-76x76@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-76x76@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/beta/ios/AppIcon-83.5x83.5@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/128x128.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/128x128@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/32x32.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/64x64.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square107x107Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square142x142Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square150x150Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square284x284Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square30x30Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square310x310Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square44x44Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square71x71Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/Square89x89Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/StoreLogo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/android/values/ic_launcher_background.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/icon.icns` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/icon.ico` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/icon.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-512@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/128x128.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/128x128@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/32x32.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/64x64.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square107x107Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square142x142Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square150x150Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square284x284Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square30x30Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square310x310Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square44x44Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square71x71Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/Square89x89Logo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/StoreLogo.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/android/values/ic_launcher_background.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/icon.icns` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/icon.ico` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/icon.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-512@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/release/appstream.metainfo.xml` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/cli.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/constants.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/lib.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/linux_display.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/linux_windowing.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/logging.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/main.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/markdown.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/os/mod.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/os/windows.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/server.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/window_customizer.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/src/windows.rs` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/tauri.beta.conf.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/tauri.conf.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src-tauri/tauri.prod.conf.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/bindings.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/cli.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/entry.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/ar.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/br.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/bs.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/da.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/de.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/en.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/es.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/fr.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/index.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/ja.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/ko.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/nl.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/no.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/pl.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/ru.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/zh.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/i18n/zht.ts` (i18n) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/index.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/loading.tsx` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/menu.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/styles.css` (other) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/updater.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/src/webview-zoom.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/tsconfig.json` (config) + - File should be skipped (does not exist in Kilo fork) +- `packages/desktop/vite.config.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/.gitignore` (other) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/.vscode-test.mjs` (other) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/.vscodeignore` (other) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/README.md` (markdown) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/bun.lock` (other) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/esbuild.js` (code) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/eslint.config.mjs` (other) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/images/button-dark.svg` (other) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/images/button-light.svg` (other) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/images/icon.png` (other) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/package.json` (package) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/script/publish` (script) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/script/release` (script) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/src/extension.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/sst-env.d.ts` (code) + - File should be skipped (does not exist in Kilo fork) +- `sdks/vscode/tsconfig.json` (config) + - File should be skipped (does not exist in Kilo fork) + +### i18n Transform (Auto-Apply Kilo Branding) + +- `packages/kilo-web-ui/src/i18n/ar.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/br.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/bs.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/da.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/de.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/en.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/es.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/fr.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/ja.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/ko.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/nl.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/no.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/pl.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/ru.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/th.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/tr.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/uk.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/zh.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding +- `packages/kilo-web-ui/src/i18n/zht.ts` (i18n) + - i18n file: take upstream translations and apply Kilo branding + +### Take Upstream + Kilo Branding (Auto) + +- `packages/ui/src/components/file-ssr.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms +- `packages/ui/src/components/file.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms +- `packages/ui/src/components/line-comment.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms +- `packages/ui/src/components/session-review.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms +- `packages/ui/src/components/tool-error-card.stories.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms +- `packages/ui/src/components/tool-error-card.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms +- `packages/ui/src/components/tool-status-title.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms +- `packages/ui/src/kilocode/markdown-mermaid-actions.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms +- `packages/ui/src/kilocode/markdown-mermaid.stories.tsx` (code) + - Branding-only file: take upstream and apply Kilo branding transforms + +### Package.json Transform (Auto) + +- `package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/core/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/http-recorder/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/kilo-console/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/kilo-indexing/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/kilo-web-ui/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/llm/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/opencode/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/plugin-atomic-chat/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/plugin/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/sdk/js/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/shared/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/storybook/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version +- `packages/ui/package.json` (package) + - Package.json: take upstream, transform names, inject Kilo deps, preserve version + +### Script Transform (Auto) + +- `.kilo/skills/release-jetbrains/script/dispatch-prepare.ts` (script) + - Script file: take upstream and transform GitHub references +- `.kilo/skills/release-jetbrains/script/resolve-version.ts` (script) + - Script file: take upstream and transform GitHub references +- `.kilo/skills/release-jetbrains/script/update-changelog.ts` (script) + - Script file: take upstream and transform GitHub references +- `.kilo/skills/release-jetbrains/script/watch-publish.ts` (script) + - Script file: take upstream and transform GitHub references +- `packages/llm/script/recording-cost-report.ts` (script) + - Script file: take upstream and transform GitHub references +- `packages/llm/script/setup-recording-env.ts` (script) + - Script file: take upstream and transform GitHub references +- `packages/opencode/script/httpapi-exercise.ts` (script) + - Script file: take upstream and transform GitHub references +- `packages/opencode/script/kilocode/models-snapshot.ts` (script) + - Script file: take upstream and transform GitHub references +- `packages/opencode/script/schema.ts` (script) + - Script file: take upstream and transform GitHub references +- `packages/opencode/script/upgrade-opentui.ts` (script) + - Script file: take upstream and transform GitHub references +- `packages/script/package.json` (script) + - Script file: take upstream and transform GitHub references +- `packages/sdk/js/script/build.ts` (script) + - Script file: take upstream and transform GitHub references +- `script/beta.ts` (script) + - Script file: take upstream and transform GitHub references +- `script/github/close-issues.ts` (script) + - Script file: take upstream and transform GitHub references +- `script/upgrade-opentui.ts` (script) + - Script file: take upstream and transform GitHub references + +### Extension Transform (Auto) + +- `packages/extensions/zed/extension.toml` (extension) + - Extension file: take upstream and apply Kilo branding + +### Keep Kilo Version (Ours) + +- `.changeset/center-sidebar-toolbar.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/chilly-dancers-build.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/external-directory-read-prompts.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/fuzzy-berries-press.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/green-planes-reply.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/remote-status-badge.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/slow-terms-dance.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/soft-garlic-care.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/terminal-command-display.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.changeset/tidy-otters-wait.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.github/pull_request_template.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `.github/workflows/close-stale-prs.yml` (config) + - File is Kilo-specific and should not be overwritten +- `.github/workflows/publish.yml` (config) + - File is Kilo-specific and should not be overwritten +- `.kilo/agent/upstream-merge.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/command/review-upstream-merge.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1779979921672-mighty-river.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1779979972859-crisp-comet.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1779980012659-happy-circuit.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1779980572375-proud-river.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1779987162764-misty-rocket.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1779987392284-clever-river.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1779989384508-mighty-cabin.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1779990397226-quiet-canyon.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/plans/1780256109130-stellar-forest.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/skills/gh-issues/SKILL.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/skills/jetbrains-ui-style/SKILL.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/skills/kilocode-merge-minimizer/SKILL.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.kilo/skills/release-jetbrains/SKILL.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.opencode/agent/triage.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.opencode/command/changelog.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.opencode/skills/effect/SKILL.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.opencode/skills/improve-codebase-architecture/DEEPENING.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.opencode/skills/improve-codebase-architecture/INTERFACE-DESIGN.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.opencode/skills/improve-codebase-architecture/LANGUAGE.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `.opencode/skills/improve-codebase-architecture/SKILL.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `AGENTS.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `CONTRIBUTING.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `REVIEW.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `TESTING.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/core/src/github-copilot/README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/http-recorder/README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/kilo-docs/__tests__/heading.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/__tests__/sitemap.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/BrowserFrame/index.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/css.d.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/diagrams/adversarial-loop.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/diagrams/bead-lifecycle.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/diagrams/claim-to-stamp.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/diagrams/convoy-execution.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/diagrams/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/diagrams/wanted-lifecycle.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/diagrams/wasteland-federation.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/FlowDiagram/index.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/Image.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/ImageGallery.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/TableOfContents.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/Tabs.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/components/index.js` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/lib/nav/ai-providers.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/lib/nav/code-with-ai.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/lib/nav/contributing.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/lib/nav/kiloclaw.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/lychee.toml` (config) + - File is in a Kilo-specific directory +- `packages/kilo-docs/markdoc/nodes/heading.markdoc.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/markdoc/partials/cli-commands-table.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/markdoc/partials/install-jetbrains.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/markdoc/tags/browser-frame.markdoc.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/markdoc/tags/flow-diagram.markdoc.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/markdoc/tags/image-gallery.markdoc.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/markdoc/tags/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/markdoc/tags/linebreak.markdoc.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/next.config.js` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/package.json` (package) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/_app.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/anthropic.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/atomic-chat.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/bedrock.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/cerebras.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/chutes-ai.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/claude-code.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/deepseek.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/fireworks.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/gemini.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/glama.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/groq.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/human-relay.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/inception.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/kilocode.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/lmstudio.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/minimax.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/mistral.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/moonshot.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/ollama.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/openai-compatible.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/openai.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/openrouter.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/ovhcloud.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/requesty.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/sap-ai-core.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/synthetic.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/unbound.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/v0.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/vertex.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/virtual-quota-fallback.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/vscode-lm.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/xai.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/ai-providers/zenmux.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/api/sitemap.xml.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/agent-manager-workflows.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/agent-manager.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/extending/local-models.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/extending/plugins.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/how-tools-work.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/access-mcp-resource.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/apply-diff.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/ask-followup-question.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/attempt-completion.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/browser-action.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/delete-file.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/execute-command.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/list-code-definition-names.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/list-files.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/new-task.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/read-file.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/search-files.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/semantic-search.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/switch-mode.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/update-todo-list.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/use-mcp-tool.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/automate/tools/write-to-file.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/agents/auto-model.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/agents/chat-interface.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/agents/custom-models.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/agents/model-selection.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/agents/using-agents.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/features/autocomplete/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/features/file-encoding.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/features/git-commit-generation.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/features/message-feedback.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/features/speech-to-text.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/code-review.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/concepts.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/mayor.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/quick-start.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/settings.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/sling-work.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/troubleshooting.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/wasteland/admin.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/wasteland/concepts.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/wasteland/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/wasteland/quick-start.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/wasteland/settings.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/gastown/wasteland/workflow.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/cli.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/github.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/jetbrains.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/kilo-connect.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/linear.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/mobile.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/slack.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/vscode/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/code-with-ai/platforms/vscode/whats-new.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/collaborate/teams/billing.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/agent-observability.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/auto-model-tiers.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/automation-services.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/benchmarking.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/cli-runtime.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/cloud-platform.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/cloud-security.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/config-schema.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/development-patterns.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/enterprise-mcp-controls.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/feature-template.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/features.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/jetbrains-plugin.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/mcp-oauth-authorization.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/onboarding-improvements.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/organization-modes-library.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/track-repo-url.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/voice-transcription.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/architecture/vscode-extension.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/development-environment.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/features/agent-observability.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/features/benchmarking.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/features/enterprise-mcp-controls.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/features/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/features/onboarding-improvements.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/features/template.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/contributing/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/customize/agents-md.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/customize/context/codebase-indexing.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/customize/context/context-condensing.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/customize/custom-instructions.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/customize/custom-modes.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/customize/skills.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/deploy-secure/security-reviews.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/gateway/authentication.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/gateway/models-and-providers.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/gateway/sdks-and-frameworks.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/getting-started/adding-credits.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/getting-started/byok.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/getting-started/installing.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/getting-started/settings/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/getting-started/setup-authentication.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/getting-started/troubleshooting/troubleshooting-extension.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/getting-started/using-kilo-for-free.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/index.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/kiloclaw/chat-platforms/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/kiloclaw/dashboard.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/kiloclaw/end-to-end.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/kiloclaw/faq/pricing.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/kiloclaw/overview.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/kiloclaw/tools/index.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/pages/kiloclaw/tools/other-tools.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-docs/previous-docs-redirects.js` (code) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/github/github-bug.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/github/github-issue.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/github/github-review.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/linear/linear-fix-issue.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/linear/linear-multi-repo.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/linear/linear-understand-issue.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/slack/slackbot-ask-questions.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/slack/slackbot-bugs.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/slack/slackbot-coding.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/connect/slack/slackbot-turn-discussions-into-PRs.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-beads-page-detail.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-beads-page.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-merge-queue-page-review-detail.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-merge-queue-page.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-new-rig.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-new-town-onboarding.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-rig-page-convoy-bead-in-review.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-rig-page-convoy-in-progress.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-rig-page-convoy-review-bead-detail.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-rig-page-staged-convoy-detail.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-rig-page-staged-convoy.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/gt-town-overview.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/gt-bead-with-wasteland-link.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/gt-claim-detail-drawer.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/gt-mayor-claiming.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/gt-wasteland-connect-dialog.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/gt-wasteland-settings.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/wl-admin-review-inbox.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/wl-claim-drawer.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/wl-evidence-submitted.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/wl-post-form.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/gastown/wasteland/wl-wanted-board.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/kiloclaw/kiloclaw-architecture.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/mobile-apps/home.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/mobile-apps/kiloclaw-chat.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/mobile-apps/new-session.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/mobile-apps/session-chat.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/mobile-apps/session-filters.webp` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/npm-package-readme/kilo-cli.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/organization-modes-library-1.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/organization-modes-library-2.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/diff-panel-with-diffs-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-bulk-action-collapse-all-button-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-bulk-action-expand-all-button-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-with-changes-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/full-screen-diff-with-collapsed-context-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/inline-diff-bulk-action-collapse-all-button-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/inline-diff-bulk-action-expand-all-button-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/tab-bar-multiple-tabs-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/tab-bar-single-tab-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/tab-bar-with-review-tab-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/chat-view-agent-manager-completed-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/error-display-data-policy-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-subagent-to-queued-user-spacing-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/message-list-tool-to-queued-user-spacing-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/question-dock-many-options-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/question-dock-multi-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/question-dock-single-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/suggest-bar-review-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/task-header-with-todos-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/turn-outcome-failed-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/turn-outcome-unknown-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/user-message-review-comments-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/components-shell/shell-execution-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/background-process-tool-cards-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/bash-with-permission-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/diff-summary-collapsed-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/mcp-tool-expanded-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-apply-patch-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-bash-many-rules-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-edit-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-heredoc-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-subagent-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/tool-errors-200-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/tool-errors-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/history-sessionlist/sources-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/history-sessionlist/with-items-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/installed-agent-card-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/modes-tab-empty-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/modes-tab-with-installed-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/modes-tab-with-items-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/single-agent-card-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/agent-behaviour-agents-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/agent-behaviour-edit-custom-mode-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/agent-behaviour-workflows-empty-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-kilo-catalog-loading-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-kilo-model-preset-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-provider-blur-race-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-scope-switch-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/mode-edit-export-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/mode-edit-permissions-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-accessible-labels-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-autocomplete-open-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/providers-configure-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/settings-panel-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-200-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-default-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/shared/model-selector-accessible-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/shared/model-selector-selected-favorite-chromium-linux.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/track-repo-url-system-design.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/public/img/voice-transcription-architecture.png` (other) + - File is in a Kilo-specific directory +- `packages/kilo-docs/source-links.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/.gitignore` (other) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/package.json` (package) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/api/constants.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/api/embedding-models.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/api/models.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/api/modes.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/api/url.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/auth/token.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/autocomplete.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/cloud-sessions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/edit-prompt.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/edit.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/fim.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/headers.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/mistral-fim-endpoint.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/provider-debug.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/provider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/responses.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/server/edit.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/server/fim.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/server/handlers.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/server/routes.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/tui/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/src/types/tui.d.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/api/embedding-models.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/api/models.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/api/url.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/autocomplete.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/cloud-sessions.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/edit-prompt.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/edit.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/fim.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/mistral-fim-endpoint.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/provider.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-gateway/test/responses.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/package.json` (package) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/ar.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/br.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/bs.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/da.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/de.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/en.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/es.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/fr.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/it.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/ja.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/ko.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/nl.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/no.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/pl.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/ru.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/th.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/tr.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/uk.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/zh.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-i18n/src/zht.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/.gitignore` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/.run/Run IDE (Backend).run.xml` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/.run/Run IDE (Frontend).run.xml` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/AGENTS.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/kilo-jetbrains/CHANGELOG.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/kilo-jetbrains/RELEASE_TODO.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/RELEASING.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/build.gradle.kts` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloAppState.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendChatManager.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendConnectionService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendMigrationManager.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendModelStateManager.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloBackendCliManager.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloBackendHttpClients.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliConfigPath.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/dev/KiloDevMode.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/KiloBackendLegacyMigrationStoreService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/LegacyMigrationBackend.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/LegacyMigrationConverters.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/LegacyMigrationEngine.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/LegacyMigrationHttpBackend.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/LegacyMigrationJson.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/LegacyMigrationModels.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/LegacyMigrationStore.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/LegacyProviderMapping.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/session/LegacySessionIds.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/session/LegacySessionMessages.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/session/LegacySessionParser.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/session/LegacySessionParts.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/migration/session/LegacySessionPath.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloAppRpcApiImpl.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloMigrationRpcApiImpl.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloMigrationRpcApiProvider.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloSessionRpcApiImpl.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceDtoMapper.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/MigrationRpcMapper.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/telemetry/KiloBackendTelemetry.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspace.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorkspaceState.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/main/resources/kilo.jetbrains.backend.xml` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendAppServiceTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendChatManagerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendModelStateManagerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendSessionManagerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloConnectionServiceTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/ChatDtoSerializationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/ChatLogSummaryTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/GeneratedApiModelSerializationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloBackendCliManagerEnvTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/ProjectModelSerializationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/SessionModelSerializationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/migration/LegacyMigrationConversionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/migration/LegacyMigrationDetectionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/migration/LegacyMigrationHttpBackendTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/migration/LegacyMigrationOrchestrationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/migration/LegacyMigrationSessionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/migration/LegacySettingsFileFixture.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/telemetry/KiloBackendTelemetryTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/testing/MockCliServer.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/testing/TestLog.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspaceTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/build.gradle.kts` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/settings.gradle.kts` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/src/main/kotlin/BuildTasksPlugin.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/src/main/kotlin/CheckCliTask.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/src/main/kotlin/FixGeneratedApiTask.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/src/main/kotlin/GenerateOpenApiSpecTask.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/src/main/kotlin/PrepareLocalCliTask.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/src/main/kotlin/normalization/NormalizeOpenApiSpecTask.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/src/main/kotlin/normalization/OpenApiSpecNormalizer.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build-tasks/src/test/kotlin/normalization/OpenApiSpecNormalizerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/build.gradle.kts` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/build.gradle.kts` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloNotifications.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/DeleteSessionAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/HistoryAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/KiloSettingsAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/NewSessionAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/OpenConfigActions.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/OpenSessionAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/OpenSettingsAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/ReinstallKiloAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/RenameSessionAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/RestartKiloAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/SendPromptAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/ShowProfileAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/StopSessionAction.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloAppService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloSessionService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloWorkspaceService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/autocomplete/KiloAutocompleteSettingsService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/migration/KiloMigrationService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/migration/MigrationSelectionBuilder.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/migration/MigrationUiState.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/migration/ui/MigrationItemRow.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/migration/ui/MigrationOverlayPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/migration/ui/MigrationStatusIcon.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/migration/ui/MigrationWizardPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/plugin/KiloPluginSettings.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionActivityKind.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionManager.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionRef.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUiFactory.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/PaidModelAuth.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionControllerEvent.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionQueueCondenser.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionUpdateQueue.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/GitRemoteUrl.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryActivitySnapshot.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryController.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryDataKeys.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryItem.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryListRenderer.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryListUi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryModel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryTime.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/Message.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/Permission.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/Question.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/SessionModel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/SessionModelEvent.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/SessionState.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/ScrollButtonIcon.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/SessionScroll.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/ConnectionPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/EmptySessionPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/LabelPicker.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/LoadingPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/PermissionPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/PickerRow.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/ProgressPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/PromptPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/QuestionPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/ReasoningPicker.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionLayout.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionMessageListPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionRootPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/account/AccountChoice.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/account/AccountPickerRenderer.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlay.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/editor/SessionEditorTextField.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionFeedback.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/RecentsList.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/header/ContextBar.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/header/HeaderFormat.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/header/RotatedIcon.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/header/SessionHeaderPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/header/TimelinePanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/mode/ModePicker.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/mode/ModePickerRenderer.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/model/ModelPicker.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/model/ModelPickerRenderer.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/model/ModelPickerRows.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptDataKeys.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptEditorTextField.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/SendPromptContext.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/selection/SessionSelection.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionEditorStyle.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/update/SessionController.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/update/SessionControllerEvent.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/update/SessionUpdateQueue.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/CompactionView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/GenericView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/LoginRequiredView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/MessageView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PartView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PlanExitView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/ReasoningView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/SessionViewIcons.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/TextView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/ToolView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/TurnView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/ViewFactory.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/AbstractSessionPartView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/BaseQuestionView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/GenericView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/PartView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/PrimarySessionPartView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/SecondarySessionPartView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/permission/PermissionDiffView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/permission/PermissionView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionResultParser.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionResultView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/todo/TodoListPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/todo/TodoWriteView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/BaseSearchToolView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/GlobToolView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/ReadToolView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/SearchToolView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/ToolSupport.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/ToolView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/KiloSettingsConfigurable.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/KiloSettingsSelection.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/BaseContentPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/BaseSettingsUi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsProgressOverlay.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsRow.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsTop.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/models/ModelSettingPicker.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/models/ModelsConfigurable.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/models/ModelsSettingsState.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/models/ModelsSettingsUi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/BalanceFormat.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedInProfileUi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoginState.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/QrCode.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/UserProfileConfigurable.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/telemetry/KiloTelemetryService.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/DiffStatBadge.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/FilledBadgeIcon.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/HoverIcon.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/LayeredOverlayPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/PickerButton.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/RoundedContentPanel.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/layout/Align.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/layout/Stack.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdCommon.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdView.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdViewFactory.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdViewHtmlPane.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdViewHybrid.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/arrow-down-to-line.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/arrow-down-to-line_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/arrow-up.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/arrow-up_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/book-open-check.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/book-open-check_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/chevron-down.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/chevron-down_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/compress.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/compress_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/discord.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/discord_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/kilo-profile.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/kilo-profile_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/plus.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/plus_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-bottom_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/scroll-question_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/shield-filled.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/shield-filled_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/shield.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/shield_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/brain.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/brain_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/bubble-5.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/bubble-5_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/bullet-list.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/bullet-list_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/checklist.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/checklist_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/chevron-down.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/chevron-down_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/chevron-left.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/chevron-left_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/chevron-right.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/chevron-right_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/code-lines.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/code-lines_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/code.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/code_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/console.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/console_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/eye.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/eye_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/glasses.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/glasses_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/magnifying-glass-menu.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/magnifying-glass-menu_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/mcp.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/mcp_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/task.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/task_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/warning.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/warning_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/window-cursor.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/views/window-cursor_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/wand-sparkles.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/icons/wand-sparkles_dark.svg` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_ar.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_bs.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_da.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_de.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_es.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_fr.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_ja.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_ko.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_nl.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_no.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_pl.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_pt_BR.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_ru.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_th.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_tr.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_uk.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_zh_CN.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle_zh_TW.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/actions/HistorySessionActionsTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/actions/KiloRecoveryActionsTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/actions/NewSessionActionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/actions/SendPromptActionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/actions/StopSessionActionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloAppServiceTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloSessionServiceTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloWorkspaceServiceTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/migration/FakeMigrationUiController.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/migration/KiloMigrationServiceTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/migration/SessionUiMigrationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionRefTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionSidePanelManagerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiFactoryTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiLayoutTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiTestBase.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/AppWatchingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/ChatLoggingFlowTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/ConfigSelectionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/ConnectionDelayTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DisposedStateTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/HistoryLoadingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/JsonSessionStreamTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/ListenerLifecycleTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/MessageListTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/PaidModelAuthTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/ProgressTrackingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/PromptEnhancerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/PromptLifecycleTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionArtifactsTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionCreationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionHeaderControllerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionQueueCondenserTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionRecoveryTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionUpdateQueueTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/StatusComputationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/TurnLifecycleTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/ViewSwitchingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/WorkspaceWatchingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/history/HistoryActivitySnapshotTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/history/HistoryControllerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/model/SessionModelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/model/TurnGroupingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/ConnectionPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/EmptySessionPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/ProgressPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/PromptPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/QuestionPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionEditorStyleTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionLayoutTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionMessageListPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionRootPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionSelectionCopyTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionUiUpdateTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlayTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/header/SessionHeaderPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/mode/ModePickerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/model/ModelPickerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/selection/SessionSelectionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/ConfigSelectionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/HistoryLoadingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/PromptLifecycleTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/SessionCreationTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/SessionRecoveryTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/SessionUpdateQueueTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/TurnLifecycleTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/ViewSwitchingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/update/WorkspaceWatchingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/GlobToolViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/LoginRequiredViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/PlanExitViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/QuestionResultViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/QuestionViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/ReadToolViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/ReasoningViewStressTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/ReasoningViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/SearchToolViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/TextViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/ToolBodyStressTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/ToolViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/TurnViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/base/AbstractSessionPartViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/base/BaseQuestionViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/permission/PermissionViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/question/QuestionResultParserTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/todo/TodoWriteViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/KiloSettingsConfigurableTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/KiloSettingsSelectionTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/QrCodeTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/UserProfileConfigurableTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/base/BaseSettingsUiTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/base/BaseSettingsUiWorkspaceTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/base/SettingsRowsTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/models/ModelSettingPickerTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/models/ModelsSettingsStateTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/models/ModelsSettingsUiTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeAppRpcApi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeMigrationRpcApi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeSessionRpcApi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeWorkspaceRpcApi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/LayeredOverlayPanelTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/UiStyleTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/layout/AlignTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/layout/StackTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/md/MdViewFactoryTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/md/MdViewHybridStressTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/md/MdViewHybridTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/md/MdViewLoggingTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/md/MdViewTest.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/gradle.properties` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/gradle/libs.versions.toml` (config) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/package.json` (package) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/script/build-version.sh` (script) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/script/build.ts` (script) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/script/dev/part-update.sh` (script) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/script/test-ci.ts` (script) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/KiloPlugin.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/log/ChatLogSummary.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/log/KiloLog.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloAppRpcApi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloMigrationRpcApi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloSessionRpcApi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloWorkspaceRpcApi.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ChatDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ConfigTargetDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/HistoryDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/KiloAppStateDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/KiloWorkspaceStateDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/MigrationDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ModelStateDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ProviderDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/SkillDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/TelemetryDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/WorkspaceFileDto.kt` (other) + - File is in a Kilo-specific directory +- `packages/kilo-telemetry/package.json` (package) + - File is in a Kilo-specific directory +- `packages/kilo-telemetry/src/__tests__/telemetry.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-telemetry/src/events.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-telemetry/src/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-telemetry/src/otel-exporter.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-telemetry/src/telemetry.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-telemetry/src/tracer.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/package.json` (package) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/accordion.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/auto-approve-bar.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/basic-tool.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/basic-tool.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/button.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/card.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/chat-input.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/checkbox.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/code.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/code.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/context-menu.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/context-tool-results.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/dialog.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/diff-changes.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/diff-ssr.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/diff.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/dropdown-menu.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/error-details.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/error-details.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/icon-button.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/icon.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/inline-input.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/list.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/markdown.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/message-part.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/message-part.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/message-row.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/model-info-card.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/model-selector.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/prompt-input.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/reasoning-heading.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/reasoning-heading.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/select.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/session-diff.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/session-diff.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/shell-rolling-results.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/status-indicator.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/switch.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/tabs.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/tag.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/task-header.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/text-field.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/tool-open-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/components/tooltip.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/hooks/auto-scroll.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/hooks/create-auto-scroll.test.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/hooks/create-auto-scroll.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/hooks/scroll-user-activity.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/i18n/it.ts` (i18n) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/pierre/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/stories/message-part.stories.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/styles/globals.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/styles/vscode-bridge.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-ui/src/util/escape-html.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/.storybook/main.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/.storybook/preview.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/AGENTS.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/kilo-vscode/CHANGELOG.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/LICENSE` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/kilo-vscode/docs/chat-ui-features/mermaid-diagram-features.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/mercury-next-edit-testing.html` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/01_finish_function_body.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/02_pattern_continuation.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/03_typo_completion.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/04_loop_body.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/05_class_method.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/07_multiline_rename_refactor.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/08_mixed_insert_and_replace.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/10_mid_token_completion.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/11_fill_sibling_method.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/12_type_annotation_insertion.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/13_docstring_generation.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/14_no_op_suppression.py` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/INSTRUCTIONS.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/go_07_error_handling.go` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/go_08_struct_method.go` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/go_09_goroutine_channel.go` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/js_07_async_await.js` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/js_08_express_route.js` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/md_07_prose_negative.md` (markdown) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/rs_07_match_arms.rs` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/rs_08_result_chain.rs` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/rs_09_lifetimes.rs` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/sql_07_join.sql` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/sql_08_where_filter.sql` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/ts_07_array_transform.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/ts_08_param_types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/docs/nes-examples/ts_09_jsx_handler.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/esbuild.js` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/eslint.config.mjs` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/knip.json` (config) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/package.json` (package) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/script/build.ts` (script) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/script/dev-snapshot.ts` (script) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/script/ffmpeg-helper.ts` (script) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/script/launch.ts` (script) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/script/local-bin.ts` (script) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/script/watch-cli.ts` (script) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/DiffViewerProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/DiffVirtualProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/KiloProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/MarketplacePanelProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/SettingsEditorProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/SubAgentViewerProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/GitOps.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/SessionTerminalManager.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/WorktreeManager.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/__tests__/AgentManagerProvider.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/constants.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/continue-in-worktree.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/fork-handoff.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/fork-session.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/host.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/local-diff.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/mcp-warmup.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/promotion-handoff.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/setup-script-template.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/state-recovery.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/terminal-font.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/terminal-routing.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/tool-start.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/vscode-host.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/commands/toggle-auto-approve.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/DiffViewerProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/SourceController.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/polling.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/shared/binary.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/shared/hash.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/shared/reverter.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/shared/target.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/sources/catalog.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/sources/git-status.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/sources/session.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/sources/staged.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/sources/turn.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/sources/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/sources/unstaged.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/sources/worktree.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/diff/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/enhance-prompt-error.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/extension.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/features.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/background-process.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/early-message.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/editor-actions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/export-transcript.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/file-search-items.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/file-search-results.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/file-search.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/font-size.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/fork-session.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/git-changes-target.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/handlers/cloud-session.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/handlers/permission-handler.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/handlers/question.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/mcp-oauth.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/message-page.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/native-tab-title.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/options.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/remove-config-item.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/rename-session.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/save-image.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/session-stream-scheduler.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/sidebar-worktree.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/slim-metadata.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/visible-task-streams.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/work-style-apply-handler.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/work-style-apply.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kilo-provider/work-style.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kiloclaw/KiloClawProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kiloclaw/chat-client.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kiloclaw/event-service-client.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kiloclaw/kilo-chat-client.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kiloclaw/token-manager.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kiloclaw/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/kiloclaw/ulid.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/provider-actions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/review-settings.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/review-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/AutocompleteModel.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/AutocompleteServiceManager.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteModel.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/__tests__/AutocompleteServiceManager.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/__tests__/settings.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/ErrorBackoff.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/FillInTheMiddle.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteContextProvider.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/AutocompleteInlineCompletionProvider.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/__tests__/uselessSuggestionFilter.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/getProcessedSnippets.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/context/VisibleCodeTracker.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/constants/AutocompleteLanguageInfo.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ranking/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/static-context/StaticContextService.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/snippets/getAllSnippets.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/AutocompleteTemplate.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/__tests__/formatOpenedFilesContext.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/templating/filtering.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/diff/util.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/diff/util.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/messageContent.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/parameters.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/ranges.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/util/treeSitter.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/autocomplete/lsp.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/fim.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/migrate-default.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/MercuryEditProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/NextEditInlineCompletionProvider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/NextEditSuggestionManager.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/constants.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/editHistoryTracker.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/editableRegion.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/log.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/pendingEdit.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/recentSnippetsAdapter.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/next-edit/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/settings.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/autocomplete/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/browser-automation/browser-automation-service.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/cli-backend/cli-resources.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/cli-backend/connection-service.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/cli-backend/connection-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/cli-backend/i18n/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/cli-backend/i18n/it.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/cli-backend/retry.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/cli-backend/server-manager.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/code-actions/register-code-actions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/code-actions/register-terminal-actions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/commit-message/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/input-tools.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/marketplace/actions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/marketplace/api.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/marketplace/detection.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/marketplace/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/marketplace/installer.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/marketplace/paths.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/marketplace/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/telemetry/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/telemetry/telemetry-proxy.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/telemetry/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/services/telemetry/webview-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/session-status.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/shared/autocomplete-models.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/shared/custom-provider.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/shared/provider-model.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/shared/review-comments.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/shared/session-title.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/shared/stream-messages.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/shared/work-style-presets.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/speech-to-text/capture.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/speech-to-text/handler.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/speech-to-text/models.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/speech-to-text/transcribe.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/util/spotlight.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/webview-html-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/src/worktree-diff-client.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/accessibility.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/diff-scroll-preservation.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/history-accessibility.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/indexing-provider-blur-race.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/markdown-incremental-dom.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/model-selector-accessibility.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/package.json` (package) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/setup/vscode-mock.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/abort-state.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-behaviour-patches.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-manager-close-session.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-manager-diff-state.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-manager-i18n-split.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-manager-initial-message.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-manager-mcp-warmup.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-manager-terminal-font.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/auto-approve.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/autocomplete-error-backoff.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/autocomplete-migrate-default.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/autocomplete-model-selector.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/autocomplete-models-sync.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/autocomplete-settings-message.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/background-process.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/cloud-session-handler.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/config-scope.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/config-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/connection-service-question.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/connection-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/continue-in-worktree.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/custom-provider.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/diff-hash.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/diff-session-source.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/diff-source-catalog.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/diff-turn-source.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/diff-viewer-css-arch.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/errorUtils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/export-transcript.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/feedback-payload.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/file-mention-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/file-search-items.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/file-search-results.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/file-tree.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/font-size-arch.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/fork-handoff.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/fork-session.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/i18n-keys.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/indexing-tab-state.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/indexing-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-provider-followup.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-provider-indexing-refresh.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-provider-load-messages.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-provider-rename.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-provider-session-refresh.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-provider-utils-enhance-error.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-provider-worktree-context.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/local-diff.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/markdown-annotation-layer.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/markdown-raf-coalesce.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/marketplace-actions.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/marketplace-installer.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/marketplace-panel-arch.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/mode-model.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/model-preview-data-line.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/model-selector-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/native-tab-title.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/navigate.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/next-edit-editable-region.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/next-edit-history-tracker.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/next-edit-inline-completion-provider.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/next-edit-pending-edit.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/next-edit-recent-snippets.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/open-config.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/part-stash.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/permission-description.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/permission-editor.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/permission-recovery.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/plan-exit.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/plan-followup-locale-keys.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/project-directory.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/promotion-handoff.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/prompt-input-connection-guard.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/prompt-input-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/provider-actions-save.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/question-dock-contract.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/question-dock-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/question-handler.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/register-code-actions.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/register-terminal-actions.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/remove-config-item.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/review-comments.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/sameDirectory.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/sdk-sse-adapter.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/section-helpers.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/server-manager-proxy-env.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/server-manager-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-model-store.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-outcome.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-parts.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-preferences.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-queue.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-status.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-stream-scheduler.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-terminal-manager.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-title.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/session-variant-store.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/settings-io.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/slim-metadata.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/source-controller.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/speech-to-text-capture.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/speech-to-text-models-sync.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/task-session.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/task-timeline-tooltip.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/task-tool-hydration.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/textshimmer-no-timer.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/timeline-geometry.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/timeline-sizes.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/todo-revert.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/tool-open-state.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/transcript-cache.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/transcript-rows.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/use-file-mention.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/use-speech-to-text.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/webview-html.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/work-style-apply.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/work-style-presets.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/work-style-state.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/worktree-manager.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/unit/worktree-state-sections.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/visual-regression.spec.mts` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tests/visual-regression.spec.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/tsconfig.json` (config) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/ApplyDialog.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/CurrentTabsMenu.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/FullScreenDiffView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/MultiModelSelector.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/SidebarToggleButton.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/agent-manager-review.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/diff-open-policy.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/diff-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/initial-message.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/navigate.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/review-annotations.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/review-preferences.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/section-helpers.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/shortcuts.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/sidebar-collapse.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/sortable-tab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/tab-rendering.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/tab-scroll.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/tab-widths.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/terminal/SortableTerminalTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/terminal/render.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/agent-manager/terminal/state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/BaseBranchPicker.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/DiffEndMarker.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/DiffPickerHeader.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/DiffViewerApp.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/FileTree.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/MarkdownAnnotationLayer.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/MarkdownDiffView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/VirtualDiffList.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/diff-open-policy.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/diff-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/file-tree-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/markdown-annotation-mutation.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/markdown-comment-ranges.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/review-annotation-speech.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/review-annotations.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-viewer/review-comments.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/diff-virtual/DiffVirtualApp.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/KiloClawApp.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/components/ChatPanel.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/components/botStatus.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/context/claw.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/context/language.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/ar.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/br.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/bs.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/da.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/de.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/en.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/es.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/fr.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/it.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/ja.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/ko.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/nl.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/no.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/pl.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/ru.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/th.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/tr.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/uk.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/zh.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/i18n/zht.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/kiloclaw.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/kiloclaw/lib/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/marketplace/MarketplaceApp.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/marketplace/index.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/pierre-worker.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/App.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/ErrorDisplay.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/PermissionCommand.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/PermissionDiff.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/SidebarEmptyState.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/TaskTimeline.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/TaskToolExpanded.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/TranscriptRow.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/VscodeToolOverrides.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/VscodeUserMessage.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/WelcomeEmptyState.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/permission-diff-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/permission-dock-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/prompt-input-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/task-tool-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/chat/transcript-cache.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/marketplace/InstallModal.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/marketplace/RemoveDialog.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/migration/migration.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/profile/DeviceAuthCard.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/profile/ProfileView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/AutocompleteTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/BrowserTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/ContextTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/IndexingTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/McpEditView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/ModeCreateView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/PermissionEditor.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/ProviderConnectDialog.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/ProviderSelectDialog.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/SettingsRow.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/agent-behaviour-patches.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/agent-behaviour/WorkflowsTab.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/autocomplete-model-selector.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/indexing-tab-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/mode-model.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/permission-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/provider-catalog.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/BranchSelect.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/DeferredPopover.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/ModelPreview.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/PopupSelector.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/SessionRenameEditor.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/TurnOutcome.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/WorkStylePicker.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/WorkingIndicator.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/model-preview-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/shared/model-selector-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/speech-to-text/SpeechToTextButton.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/speech-to-text/availability.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/speech-to-text/model-selector.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/components/speech-to-text/useSpeechToText.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/abort-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/config.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/display.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/feedback-payload.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/feedback.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/indexing-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/indexing.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/kilo-embedding-models.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/language-bridge.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/language-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/language.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/marketplace-session.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/onboarding/work-style-toasts.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/part-stash.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/server.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/session-model-store.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/session-outcome.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/session-parts.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/session-preferences.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/session-queue.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/session-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/session-variant-store.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/session.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/todo-revert.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/transcript-rows.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/work-style-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/context/work-style.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/font-size.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/hooks/usePromptHistory.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/ar.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/br.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/bs.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/da.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/de.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/en.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/es.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/fr.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/it.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/ja.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/ko.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/nl.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/no.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/pl.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/ru.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/th.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/tr.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/uk.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/zh.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/i18n/zht.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/banners.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/chat-layout.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/chat.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/dialogs.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/high-contrast.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/history.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/model-selector.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/notifications.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/permission-dock.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/plan-exit.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/prompt-dropdowns.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/prompt-input.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/question-dock.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/session-actions.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/session-title-editor.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/settings.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/suggest-bar.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/task-header.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/tool-overrides.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/styles/welcome.css` (other) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/marketplace.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/agents.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/config.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/parts.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/permissions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/providers.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/questions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/sessions.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/utils/config-scope.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/utils/config-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/utils/errorUtils.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/utils/local-providers.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/utils/plan-path.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/utils/review-comment-markdown.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/utils/timeline/geometry.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/src/utils/timeline/sizes.ts` (code) + - File is in a Kilo-specific directory +- `packages/kilo-vscode/webview-ui/tsconfig.json` (config) + - File is in a Kilo-specific directory +- `packages/llm/AGENTS.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/llm/README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/opencode/AGENTS.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/opencode/CHANGELOG.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/opencode/specs/effect/error-boundaries-plan.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/errors.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/facades.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/guide.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/http-api.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/instance-context.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/loose-ends.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/migration.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/routes.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/schema.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/server-package.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/todo.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/effect/tools.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/openapi-translation-cleanup.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/tui-plugins.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/v2/keymappings.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/v2/notifications.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/specs/v2/tui-command-shim.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/src/control-plane/dev/README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/opencode/src/kilocode/agent-manager/event.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/agent/builder.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/agent/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/atomic-chat-feature.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/background-process/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/background-process/ports.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/bell.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/bootstrap.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/autocomplete.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/chat.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/client.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/dialog-conversation-list.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/event-service-client.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/hooks.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/kilo-chat-client.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/sidebar.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/claw/view.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/command-display.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/console.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/daemon.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/mcp.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/profile.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/roll-call.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/run-message.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/run.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/app.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-process-list.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-provider.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/context/tui-config-hot-reload.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/context/tui-config.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/feedback.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/indexing-warning.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/thread.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/heap-snapshot.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/logo.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/port-warning.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/run-auto.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/cli/setup.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/command-timeout.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/commands.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/commit-message/generate.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/components/dialog-indexing.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/components/free-model-disclosure.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/components/indexing-dialog-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/components/model-info-panel-utils.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/components/model-info-panel.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/components/session-indexing.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config-injector.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config-validation.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config/config.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config/default-plugins.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config/global-stamp.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config/markdown.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config/model-state.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config/overlay.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/config/sources.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/console/assets.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/const.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/daemon/client.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/daemon/daemon.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/editor-context.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/effect/runner.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/encoding.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/enhance-prompt.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/event-wire.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/fn.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/help.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/ignore-migrator.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing-auth.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing-event.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing-feature.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing-label.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing-warning.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing-worker-client.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing-worker-protocol.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing-worker.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/indexing.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/instance.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/kilo-commands.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/kilo-errors.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/lancedb.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/mcp-migrator.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/mcp-oauth-callback.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/modes-migrator.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/paths.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/permission/allow-everything.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/permission/config-paths.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/permission/drain.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/permission/external-directory.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/permission/read.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/permission/routes.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/permission/rule.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plan-file.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plan-followup.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plugins/attention.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plugins/home-footer.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plugins/home-news.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plugins/sidebar-background-processes.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plugins/sidebar-footer.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plugins/sidebar-indexing.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plugins/sidebar-pr.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/plugins/sidebar-usage.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/project-id.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/provider-options.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/provider/codex-refresh.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/provider/models-refresh.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/provider/models-snapshot-shape.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/provider/models-snapshot.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/provider/provider.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/pty/self-command.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/question/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/remote-tui.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/review/command.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/review/local-review-uncommitted.txt` (other) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/review/local-review.txt` (other) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/review/review.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/review/types.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/review/worktree-diff.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/security/github.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/agent-builder.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/background-process.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/commit-message.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/config-console.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/enhance-prompt.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/indexing.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/kilocode.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/network.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/remote.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/session-import.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/suggestion.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/groups/telemetry.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/agent-builder.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/background-process.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/commit-message.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/config-console.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/enhance-prompt.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/indexing.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/kilocode.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/network.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/remote.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/session-import.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/suggestion.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/handlers/telemetry.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/public.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/server.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/httpapi/session-fork.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/instance.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/provider-auth-lifecycle.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/router.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/routes/commit-message.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/routes/config-rules.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/routes/indexing.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/server/server.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/capture.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/config.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/eligibility.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/envelope.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/events.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/org-sources.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/sequence.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/session-export.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/sync-subscriber.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/buffer-cap.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/chunks.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/endpoint.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/handlers.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/inbox.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/ipc.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/schema.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/scrub.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/storage.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/uploader.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/validate.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/worker/zstd.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/workspace-fiber.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-export/workspace-provider.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-import/routes.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-import/service.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-portability/cumulative-diff.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session-portability/session-diff-restore.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/compaction-chunks.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/compaction-payload-recovery.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/compaction.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/cost-propagation.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/fork.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/instruction.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/llm.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/message-order.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/overflow.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/processor.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/prompt-queue.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/prompt.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/session/tui-sync.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/shell/shell.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/skill-remove.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/skills/builtin.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/skills/kilo-config.md` (markdown) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/snapshot/diff-full.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/snapshot/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/snapshot/materialize.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/snapshot/seed.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/snapshot/track.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/soul.txt` (other) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/suggestion/index.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/suggestion/routes.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/suggestion/tool.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/suggestion/tool.txt` (other) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/suggestion/tui/prompt.tsx` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/suggestion/tui/sync.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/system-prompt.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/text-stream.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/todo-view.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/agent-manager.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/agent-manager.txt` (other) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/background-process.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/background-process.txt` (other) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/encoded-io.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/notebook.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/ods.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/plan.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/read-docx.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/read-extract.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/registry.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/semantic-search.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/semantic-search.txt` (other) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/task.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tool/xlsx.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/ts-check.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/ts-client.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tui/app-exit.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tui/config.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tui/diff.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/tui/keybinds.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/util/url.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/workflows-migrator.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/worktree-cleanup.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/kilocode/worktree-family.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/src/server/routes/instance/httpapi/AGENTS.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/opencode/src/skill/prompt/customize-opencode.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/src/sync/README.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/opencode/src/v2/provider-parity-checklist.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/test/AGENTS.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/opencode/test/EFFECT_TEST_MIGRATION.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `packages/opencode/test/kilocode/agent-global-config-dirs.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/agent-manager-tool.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/agent-permission-overrides.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/agent-skill-permissions.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/archive-security.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/ask-agent-permissions.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/background-process-tool.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/background-process.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/bash-permission-metadata.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/bedrock-claude-empty-content.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/bin-tree-sitter-env.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/builtin-skills.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cleanup.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli-run-auto-helper.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/cmd/console.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/cmd/mcp.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/cmd/run-message.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/cmd/tui/attention.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/cmd/tui/context/tui-config.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/error.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/profile.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/roll-call.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cli/tui/thread.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cloud-session-schema.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/codex-auth-refresh.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/command-branding.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/command-timeout.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/commit-message/generate.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/compaction-payload-recovery.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config-gitignore.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config-resilience.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config-validation.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config/atomic-chat-default-plugin.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config/config.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config/console-ui.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config/indexing-default-plugin.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config/opentelemetry-default.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/config/speech-to-text-config.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/console-ui.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/cost-propagation.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/custom-provider-delete.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/daemon.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/diff-full.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/edit-permission-filediff.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/encoding.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/enhance-prompt.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/ensure-plan-dir.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/external-directory-boundary.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/free-model-disclosure.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/global-config-refresh.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/help.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/indexing-auth.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/indexing-dialog-state.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/indexing-feature.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/indexing-label.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/indexing-startup.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/indexing-warning.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/indexing-worker.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/indexing-worktree.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/instruction.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/kilo-errors.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/kilo-gateway-headers.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/kilo-loader-auth.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/kilo-models-401-fallback.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/kilo-sessions.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/lancedb-runtime.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/local-model.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/local-review-command.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/logo.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/lsp-typescript-lightweight.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/mcp-branding.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/mcp-oauth-callback.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/model-cache-effect.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/model-cache-org.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/model-info-panel-utils.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/model-info-panel.types.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/nvidia-headers.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/oauth-branding.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/patch.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/permission/config-paths.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/permission/env-read.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/permission/external-directory-allow.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/permission/next.always-rules.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/permission/next.reply-http.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/permission/next.reply-routing.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/plan-exit-detection.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/plan-file.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/plan-followup.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/plugin-dependencies.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/project-config-update.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/project-id.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/prompt-dismiss-contract.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/provider-cost.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/provider-list-failed-state.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/provider-model-refresh.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/provider/models-snapshot.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/pty-self-command.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/question-dismiss-all.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/question-option-schema.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/read-directory.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/read-docx.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/read-notebook.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/read-xlsx.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/run-auto.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/run-network.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/runner-start-order.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/security-github.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/semantic-search.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/agent-builder.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/config-model-state.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/config-overlay.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/config-rules.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/config-sources.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/httpapi-kilo-edit.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/httpapi-public.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/kilo-gateway-statuses.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/permission-allow-everything.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/provider-auth-lifecycle.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/server/tui-config.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-compaction-cap.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-compaction-chunks.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-compaction-safety.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-diff-restore.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/agent.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/buffer-cap.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/capture.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/config.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/e2e.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/eligibility.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/events.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/llm.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/org-signal.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/perf.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/respawn.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/sequence.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/source-contract.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/sync-subscriber.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/chunks.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/endpoint.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/handlers.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/inbox.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/scrub.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/storage.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/uploader.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/validate.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/worker/zstd.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/workspace-fiber.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-export/workspace-provider.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-fork-remap.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-import-service.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-list.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-message-metadata.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-overflow.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-processor-empty-tool-calls.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-processor-network-offline.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-processor-retry-limit.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-processor-review-telemetry.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-prompt-permission-refresh.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-prompt-queue.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-share.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session-title-generation.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session/instruction-substitution.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session/llm.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session/platform-attribution.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/session/session.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/sessions/remote-sender.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/shell/shell.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/skill-command-autocomplete.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/skill-remove.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/snapshot-cache.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/snapshot-seed.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/snapshot-track-timeout.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/stats-subagent-cost.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/suggestion/auto-dismiss.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/suggestion/suggestion.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/suggestion/tool.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/sync-event-encoding.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/system-prompt.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/task-nesting.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/telemetry/feedback.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/terminal.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/tool-encoding.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/tool-registry-indexing-import-failure.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/tool-registry-indexing.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/tool-registry-semantic-import-failure.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/tool-task-model.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/transform-opus-4.7.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/tui-diff.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/tui-sync-event.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/tui-sync.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/util/url.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/workflows-migrator.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/worktree-family-submodule.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/worktree-family.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/worktree-project-skills.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/kilocode/worktree-remove-lock.test.ts` (code) + - File is in a Kilo-specific directory +- `packages/opencode/test/server/AGENTS.md` (markdown) + - File is Kilo-specific and should not be overwritten +- `packages/sdk/openapi.json` (config) + - File is Kilo-specific and should not be overwritten +- `script/upstream/README.md` (script) + - File is Kilo-specific and should not be overwritten +- `script/upstream/VERIFICATION_TEST.md` (script) + - File is in a Kilo-specific directory +- `script/upstream/find-conflict-markers.sh` (script) + - File is in a Kilo-specific directory +- `script/upstream/find-reset-candidates.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/fix-kilocode-markers.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/index.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/merge.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/package.json` (script) + - File is in a Kilo-specific directory +- `script/upstream/reset-to-upstream.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/lock-files.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/preserve-versions.test.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/preserve-versions.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/skip-files.test.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/transform-i18n.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/transform-package-json.test.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/transform-package-json.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/transform-take-theirs.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/transform-tauri.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/transforms/transform-web.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/config.test.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/config.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/git.test.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/git.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/markers.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/report.test.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/report.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/reset.ts` (script) + - File is in a Kilo-specific directory +- `script/upstream/utils/upstream.ts` (script) + - File is in a Kilo-specific directory +- `specs/v2/provider-model.md` (markdown) + - Markdown files are typically Kilo-specific documentation +- `specs/v2/todo.md` (markdown) + - Markdown files are typically Kilo-specific documentation + +### Manual Review Required + +- `.github/actions/setup-bun/action.yml` (config) + - Config files may have Kilo-specific settings +- `.github/actions/setup-git-committer/action.yml` (config) + - Config files may have Kilo-specific settings +- `.github/codeql/codeql-config.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/auto-docs.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/beta.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/check-forbidden-strings.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/check-kilo-generated-artifacts.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/check-md-table-padding.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/check-opencode-annotations.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/close-issues.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/codeql-kotlin.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/codeql.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/compliance-close.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/containers.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/daily-issues-recap.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/daily-pr-recap.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/disabled/compliance-close.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/notify-discord.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/pr-management.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/pr-standards.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/publish-github-action.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/release-github-action.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/review.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/stats.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/storybook.yml.disabled` (other) + - File needs manual review +- `.github/workflows/disabled/sync-zed-extension.yml.disabled` (other) + - File needs manual review +- `.github/workflows/docs-build.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/docs-check-links.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/duplicate-issues.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/generate.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/kilo-auto-close.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/nix-eval.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/nix-hashes.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/pr-management.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/pr-standards.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/prepare-jetbrains-release.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/publish-jetbrains.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/smoke-test.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/source-check-links.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/storybook.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/test-vscode.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/test.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/triage.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/typecheck.yml` (config) + - Config files may have Kilo-specific settings +- `.github/workflows/visual-regression.yml` (config) + - Config files may have Kilo-specific settings +- `.gitignore` (other) + - File needs manual review +- `.gitleaksignore` (other) + - File needs manual review +- `.kilo/package-lock.json` (config) + - Config files may have Kilo-specific settings +- `.kilocode/package-lock.json` (config) + - Config files may have Kilo-specific settings +- `.opencode-version` (other) + - File needs manual review +- `.opencode/opencode.jsonc` (other) + - File needs manual review +- `.opencode/plugins/tui-smoke.tsx` (code) + - Code files need manual review for kilocode_change markers +- `.opencode/tool/github-triage.ts` (code) + - Code files need manual review for kilocode_change markers +- `.opencode/tui.json` (config) + - Config files may have Kilo-specific settings +- `LICENSE` (other) + - File needs manual review +- `bun.lock` (other) + - File needs manual review +- `bunfig.toml` (config) + - Config files may have Kilo-specific settings +- `flake.nix` (other) + - File needs manual review +- `install` (other) + - File needs manual review +- `kilocode-2.code-workspace` (other) + - File needs manual review +- `nix/hashes.json` (config) + - Config files may have Kilo-specific settings +- `nix/kilo.nix` (other) + - File needs manual review +- `nix/node_modules.nix` (other) + - File needs manual review +- `packages/containers/bun-node/Dockerfile` (other) + - File needs manual review +- `packages/core/src/aisdk.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/auth.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/catalog.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/cross-spawn-spawner.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/effect-zod.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/effect/logger.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/effect/memo-map.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/effect/observability.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/effect/runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/filesystem.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/flag/flag.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/chat/get-response-metadata.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/chat/map-openai-compatible-finish-reason.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/chat/openai-compatible-api-types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/chat/openai-compatible-chat-options.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/chat/openai-compatible-metadata-extractor.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/chat/openai-compatible-prepare-tools.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/copilot-provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/openai-compatible-error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/convert-to-openai-responses-input.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/map-openai-responses-finish-reason.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/openai-config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/openai-error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/openai-responses-api-types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/openai-responses-language-model.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/openai-responses-prepare-tools.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/openai-responses-settings.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/tool/code-interpreter.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/tool/file-search.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/tool/image-generation.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/tool/local-shell.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/tool/web-search-preview.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/github-copilot/responses/tool/web-search.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/global.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/installation/version.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/kilocode/global.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/kilocode/models-refresh.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/kilocode/spotlight.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/location-layer.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/location.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/model.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/models-snapshot.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/models-snapshot.js` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/models.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/npm-config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/npm.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/auth.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/boot.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/env.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/layer-map.example.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/models-dev.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/alibaba.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/amazon-bedrock.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/anthropic.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/azure.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/cerebras.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/cloudflare-ai-gateway.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/cloudflare-workers-ai.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/cohere.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/deepinfra.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/dynamic.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/gateway.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/github-copilot.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/gitlab.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/google-vertex.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/google.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/groq.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/kilo.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/llmgateway.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/mistral.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/nvidia.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/openai-compatible.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/openai.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/opencode.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/openrouter.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/perplexity.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/sap-ai-core.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/togetherai.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/venice.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/vercel.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/xai.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/plugin/provider/zenmux.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/process.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/session-event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/session-message-updater.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/session-message.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/session-prompt.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/tool-output.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/array.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/binary.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/effect-flock.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/encode.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/flock.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/glob.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/hash.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/identifier.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/iife.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/lazy.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/log.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/module.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/opencode-process.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/path.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/retry.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/util/slug.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/src/v2-schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/sst-env.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/catalog.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/effect/cross-spawn-spawner.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/effect/observability.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/event.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/filesystem/filesystem.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/fixture/effect-flock-worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/fixture/flock-worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/fixture/tmpdir.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/github-copilot/convert-to-copilot-messages.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/github-copilot/copilot-chat-model.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/global.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/kilocode/effect-zod.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/kilocode/filesystem-containment.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/lib/effect.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/models.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/npm-config.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/npm.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/fixtures/provider-factory.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-alibaba.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-amazon-bedrock.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-anthropic.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-azure-cognitive-services.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-azure.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-cerebras.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-cohere.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-deepinfra.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-dynamic.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-gateway.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-github-copilot.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-gitlab.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-google-vertex-anthropic.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-google-vertex.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-google.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-groq.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-helper.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-kilo.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-llmgateway.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-mistral.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-nvidia.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-openai-compatible.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-openai.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-opencode.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-openrouter.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-perplexity.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-sap-ai-core.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-togetherai.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-venice.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-vercel.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-xai.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/plugin/provider-zenmux.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/process/process.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/util/effect-flock.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/test/util/flock.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/core/tsconfig.json` (config) + - Config files may have Kilo-specific settings +- `packages/http-recorder/src/cassette.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/src/effect.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/src/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/src/matching.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/src/recorder.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/src/redaction.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/src/redactor.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/src/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/src/websocket.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/sst-env.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/test/fixtures/recordings/record-replay/multi-step.json` (config) + - Config files may have Kilo-specific settings +- `packages/http-recorder/test/fixtures/recordings/record-replay/retry.json` (config) + - Config files may have Kilo-specific settings +- `packages/http-recorder/test/record-replay.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/http-recorder/tsconfig.json` (config) + - Config files may have Kilo-specific settings +- `packages/kilo-console/index.html` (other) + - File needs manual review +- `packages/kilo-console/public/favicon.ico` (other) + - File needs manual review +- `packages/kilo-console/public/favicon.svg` (other) + - File needs manual review +- `packages/kilo-console/public/logo.lottie` (other) + - File needs manual review +- `packages/kilo-console/src/App.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/client.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/client.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/ConfirmDialog.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/CustomSelect.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/LoadingLogo.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/LoadingScreen.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/PromptDialog.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/SearchField.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/app-header/AppHeader.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/app-header/OmniSearch.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/components/app-sidebar/AppSidebar.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/context/ConfigProvider.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/context/config.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/index.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/layouts/ConfigLayout.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/layouts/ConsoleLayout.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/AgentsRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/CliNotificationsRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/CliUiRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/ConfigPage.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/ConfigRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/ConfigSidebar.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/ConsoleUiRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/FormattersRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/IndexingRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/KeybindsRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/McpRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/ModelsRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/OverviewRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/PermissionsRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/ProvidersRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/ServersRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/SourcesRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/ToolsRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/sections.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/agents.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/console.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/console.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/formatters.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/indexing.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/indexing.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/keybinds.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/mcp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/models.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/permissions.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/providers.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/config/state/ui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/profile/ProfileRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/projects/ProjectConsoleRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/projects/ProjectsRoute.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/routes/projects/terminal/GhosttyTerminal.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/shared/navigation.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/shared/navigation.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/shared/terminal-status.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/shared/utils.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/src/styles.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/agents-tools.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/base.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/cli-ui.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/config.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/formatters.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/indexing.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/keybinds.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/loading.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/mcp.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/models.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/overview.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/permissions.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/project-console.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/projects.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/providers.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/resolved.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/responsive.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/servers.css` (other) + - File needs manual review +- `packages/kilo-console/src/styles/sources.css` (other) + - File needs manual review +- `packages/kilo-console/src/vite-env.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-console/tsconfig.json` (config) + - Config files may have Kilo-specific settings +- `packages/kilo-console/vite.config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/config-manager.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/constants/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/embedders/kilo.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/embedders/openai-compatible.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/embedders/openrouter.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/embedding-profile.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/interfaces/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/interfaces/embedder.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/interfaces/manager.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/manager.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/model-registry.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/orchestrator.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/service-factory.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/shared/validation-helpers.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/kilo-embedding-models.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/src/tree-sitter/languageParser.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/test/kilocode/indexing/config-manager.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/test/kilocode/indexing/embedders/kilo.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/test/kilocode/indexing/embedders/openrouter.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/test/kilocode/indexing/service-factory.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-indexing/test/kilocode/tree-sitter/wasm-resolution.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/accordion.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/alert-dialog.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/alert.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/animated-number.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/app-icon.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/avatar.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/badge.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/basic-tool.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/button-group.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/button.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/card.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/chart.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/checkbox.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/code.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/collapsible.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/combobox.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/config-row.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/console.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/context-menu.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/dialog.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/diff-changes.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/diff-ssr.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/diff.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/dock-prompt.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/dock-surface.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/dropdown-menu.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/empty.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/error-details.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/favicon.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/field.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/file-icon.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/file.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/font.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/hover-card.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/icon-button.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/icon.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/image-preview.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/inline-input.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/input-group.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/input.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/item.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/kbd.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/keybind.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/label.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/line-comment-annotations.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/line-comment.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/list.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/logo.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/markdown.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/message-nav.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/message-part.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/motion-spring.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/native-select.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/page-header.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/popover.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/progress-circle.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/progress.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/provider-icon.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/radio-group.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/resize-handle.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/scroll-view.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/select.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/separator.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/session-diff.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/session-review.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/session-turn.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/sheet.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/skeleton.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/slider.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/sonner.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/source-badge.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/spinner.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/status-tag.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/sticky-accordion-header.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/switch.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/table.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/tabs.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/tag.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/text-field.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/text-reveal.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/text-shimmer.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/text-strikethrough.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/textarea.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/toast.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/toggle-group.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/toggle.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/tooltip.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/typewriter.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/components/utils.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/code.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/data.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/dialog.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/diff.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/file.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/helper.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/i18n.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/marked.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/context/worker-pool.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/hooks/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/icons/app.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/icons/file-type.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/icons/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/lucide.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/pierre/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/pierre/selection-bridge.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/pierre/worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/styles/button.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/card.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/console-shell.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/console.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/form.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/index.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/keyboard.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/misc.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/responsive.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/sheet.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/tag.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/tailwind/index.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/styles/theme.css` (other) + - File needs manual review +- `packages/kilo-web-ui/src/theme/color.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/theme/context.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/theme/default-themes.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/theme/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/theme/loader.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/theme/resolve.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/theme/types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/src/vite-env.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/kilo-web-ui/tsconfig.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/example/tutorial.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/cache-policy.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/llm.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/anthropic-messages.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/bedrock-converse.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/bedrock-event-stream.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/gemini.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/openai-chat.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/openai-compatible-chat.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/openai-responses.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/utils/bedrock-auth.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/utils/bedrock-cache.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/utils/bedrock-media.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/utils/cache.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/utils/gemini-tool-schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/utils/lifecycle.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/utils/openai-options.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/protocols/utils/tool-stream.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/amazon-bedrock.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/anthropic.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/azure.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/cloudflare.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/github-copilot.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/google.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/openai-compatible-profile.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/openai-compatible.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/openai-options.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/openai.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/openrouter.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/providers/xai.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/auth-options.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/auth.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/client.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/endpoint.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/executor.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/framing.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/protocol.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/transport/http.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/transport/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/route/transport/websocket.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/schema/errors.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/schema/events.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/schema/ids.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/schema/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/schema/messages.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/schema/options.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/tool-runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/src/tool.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/sst-env.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/adapter.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/auth-options.types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/auth.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/cache-policy.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/endpoint.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/executor.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/exports.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/anthropic-messages/accepts-malformed-assistant-tool-order-with-default-patch.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/anthropic-messages/claude-opus-4-7-drives-a-tool-loop.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/anthropic-messages/rejects-malformed-assistant-tool-order-without-patch.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/anthropic-messages/streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/anthropic-messages/streams-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/bedrock-converse/drives-a-tool-loop.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/bedrock-converse/streams-a-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/bedrock-converse/streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/cloudflare-ai-gateway/cloudflare-ai-gateway-workers-ai-gpt-oss-20b-tools-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/cloudflare-ai-gateway/cloudflare-ai-gateway-workers-ai-llama-3-1-8b-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/cloudflare-workers-ai/cloudflare-workers-ai-gpt-oss-20b-tools-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/cloudflare-workers-ai/cloudflare-workers-ai-llama-3-1-8b-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/gemini/streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/gemini/streams-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-chat/continues-after-tool-result.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-chat/drives-a-tool-loop-end-to-end.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-chat/streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-chat/streams-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/deepseek-streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/groq-llama-3-3-70b-drives-a-tool-loop.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/groq-streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/groq-streams-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/openrouter-claude-opus-4-7-drives-a-tool-loop.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/openrouter-gpt-4o-mini-drives-a-tool-loop.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/openrouter-gpt-5-5-drives-a-tool-loop.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/openrouter-streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/openrouter-streams-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/togetherai-streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-compatible-chat/togetherai-streams-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-responses/gpt-5-5-drives-a-tool-loop.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-responses/gpt-5-5-streams-text.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/fixtures/recordings/openai-responses/gpt-5-5-streams-tool-call.json` (config) + - Config files may have Kilo-specific settings +- `packages/llm/test/generate-object.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/lib/effect.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/lib/http.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/lib/openai-chunks.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/lib/sse.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/lib/tool-runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/llm.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider.types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/anthropic-messages.recorded.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/anthropic-messages.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/bedrock-converse.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/cloudflare.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/gemini-cache.recorded.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/gemini.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/golden.recorded.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/openai-chat.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/openai-compatible-chat.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/openai-responses-cache.recorded.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/openai-responses.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/provider/openrouter.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/recorded-golden.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/recorded-runner.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/recorded-scenarios.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/recorded-test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/recorded-utils.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/recorded-websocket.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/schema.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/tool-runtime.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/tool-stream.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/test/tool.types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/llm/tsconfig.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/.gitignore` (other) + - File needs manual review +- `packages/opencode/bin/kilo` (other) + - File needs manual review +- `packages/opencode/migration/20260427172553_slow_nightmare/migration.sql` (other) + - File needs manual review +- `packages/opencode/migration/20260427172553_slow_nightmare/snapshot.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/migration/20260428004200_add_session_path/migration.sql` (other) + - File needs manual review +- `packages/opencode/migration/20260428004200_add_session_path/snapshot.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/migration/20260501142318_next_venus/migration.sql` (other) + - File needs manual review +- `packages/opencode/migration/20260501142318_next_venus/snapshot.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/migration/20260504145000_add_sync_owner/migration.sql` (other) + - File needs manual review +- `packages/opencode/migration/20260504145000_add_sync_owner/snapshot.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/migration/20260507164347_add_workspace_time/migration.sql` (other) + - File needs manual review +- `packages/opencode/migration/20260507164347_add_workspace_time/snapshot.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/migration/20260510033149_session_usage/migration.sql` (other) + - File needs manual review +- `packages/opencode/migration/20260510033149_session_usage/snapshot.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/migration/20260511000411_data_migration_state/migration.sql` (other) + - File needs manual review +- `packages/opencode/migration/20260511000411_data_migration_state/snapshot.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/parsers-config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/script/build.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/opencode/script/generate.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/opencode/script/postinstall.mjs` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/opencode/script/publish.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/opencode/script/test-runner.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/opencode/specs/v2/api.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/account/repo.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/acp/agent.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/acp/runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/acp/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/agent/agent.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/agent/prompt/scout.txt` (other) + - File needs manual review +- `packages/opencode/src/agent/subagent-permissions.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/audio.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/auth/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/background/job.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/bus/bus-event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/bus/global.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/bus/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/bootstrap.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/account.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/acp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/agent.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/cmd.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/db.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/agent.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/file.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/lsp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/ripgrep.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/scrap.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/skill.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/snapshot.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/startup.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/debug/v2.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/export.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/generate.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/github.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/import.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/mcp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/models.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/plug.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/pr.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/prompt-display.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/providers.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/remote.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/demo.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/entry.body.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/footer.command.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/footer.menu.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/footer.permission.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/footer.prompt.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/footer.question.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/footer.subagent.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/footer.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/footer.view.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/keymap.shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/otel.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/permission.shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/prompt.shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/question.shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/runtime.boot.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/runtime.queue.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/runtime.shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/runtime.stdin.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/scrollback.shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/scrollback.surface.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/scrollback.writer.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/session-data.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/session.shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/splash.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/stream.transport.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/stream.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/subagent-data.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/theme.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/tool.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/trace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/run/variant.shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/serve.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/stats.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/app.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/asset/charge.wav` (other) + - File needs manual review +- `packages/opencode/src/cli/cmd/tui/asset/pulse-a.wav` (other) + - File needs manual review +- `packages/opencode/src/cli/cmd/tui/asset/pulse-b.wav` (other) + - File needs manual review +- `packages/opencode/src/cli/cmd/tui/asset/pulse-c.wav` (other) + - File needs manual review +- `packages/opencode/src/cli/cmd/tui/attach.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/attention.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/bg-pulse-render.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/bg-pulse.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-go-upsell.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-retry-action.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-stash.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-workspace-file-changes.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/error-component.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/kilo-logo.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/logo.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/prompt/traits.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/spinner.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/textarea-keybindings.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/use-connected.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/component/workspace-label.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/config/keybind.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/config/tui-schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/config/tui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/aggregate-failures.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/command-palette.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/directory.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/editor-zed.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/editor.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/keybind.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/kv.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/local.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/path-format.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/plugin-keybinds.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/sdk.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/sync-v2.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/sync.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/theme.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/theme/kilo-v1.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/src/cli/cmd/tui/context/theme/kilo.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/src/cli/cmd/tui/context/thinking.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/context/tui-config.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/system/notifications.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/system/plugins.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/feature-plugins/system/which-key.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/keymap.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/layer.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/plugin/api.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/plugin/command-shim.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/plugin/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/plugin/internal.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/plugin/runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/home.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/dialog-timeline.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/index.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/network.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/question.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/thread.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/dialog-alert.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/dialog-confirm.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/dialog-help.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/dialog.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/link.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/ui/toast.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/audio.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/clipboard.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/editor.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/scroll.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/selection.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/sound.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/terminal.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/util/transcript.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/validate-session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/tui/worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/uninstall.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/upgrade.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/cmd/web.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/effect-cmd.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/effect/prompt.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/heap.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/logo.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/network.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/ui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/cli/upgrade.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/command/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/command/template/review.txt` (other) + - File needs manual review +- `packages/opencode/src/config/agent.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/attachment.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/command.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/console-state.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/formatter.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/keybinds.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/layout.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/lsp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/managed.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/markdown.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/mcp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/model-id.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/parse.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/paths.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/permission.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/plugin.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/reference.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/server.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/skills.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/config/variable.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/adapters/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/adapters/worktree.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/adaptors/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/adaptors/worktree.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/sse.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/util.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/workspace-adapter-runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/workspace-context.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/workspace.sql.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/control-plane/workspace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/data-migration.sql.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/data-migration.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/app-runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/bootstrap-runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/bridge.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/config-service.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/instance-ref.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/instance-state.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/promise.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/run-service.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/runner.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/runtime-flags.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/effect/service-use.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/env/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/event-v2-bridge.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/file/ignore.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/file/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/file/ripgrep.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/file/watcher.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/flag/flag.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/format/formatter.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/format/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/git/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/global/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/id/id.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/ide/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/image/image.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/installation/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/jschardet.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/kilo-sessions/kilo-sessions.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/kilo-sessions/remote-sender.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/lsp/client.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/lsp/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/lsp/language.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/lsp/launch.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/lsp/lsp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/lsp/server.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/markdown.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/mcp/auth.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/mcp/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/mcp/oauth-callback.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/mcp/oauth-provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/node.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/npm/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/npm/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/npmcli-config.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/patch/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/permission/evaluate.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/permission/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/permission/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/azure.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/codex.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/digitalocean.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/github-copilot/copilot.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/github-copilot/models.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/install.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/loader.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/meta.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/shared.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/plugin/xai.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/bootstrap-service.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/bootstrap.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/instance-context.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/instance-layer.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/instance-runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/instance-store.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/instance.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/project.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/project/vcs.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/auth.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/model-cache.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/model-status.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/models-snapshot.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/models.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/sdk/copilot/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/provider/transform.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/pty/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/pty/input.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/pty/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/pty/ticket.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/question/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/question/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/reference/reference.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/reference/repository-cache.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/adapter.bun.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/adapter.node.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/adapter.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/auth.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/cors.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/fence.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/global-lifecycle.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/init-projectors.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/mdns.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/middleware.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/projectors.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/proxy-util.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/proxy.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/control/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/control/workspace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/global.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/enhance-prompt.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/experimental.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/file.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/api.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/errors.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/control.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/file.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/global.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/instance.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/mcp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/metadata.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/permission.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/project.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/pty.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/query.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/question.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/sync.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/tui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/v2.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/v2/location.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/v2/message.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/groups/workspace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/control.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/file.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/instance.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/mcp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/permission.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/project.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/question.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/tui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/v2/message.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/v2/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/handlers/workspace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/compression.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/cors-vary.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/fence.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/instance-context.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/permission.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/project.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/public.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/question.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/server.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/websocket-tracker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/httpapi/workspace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/kilocode.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/mcp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/middleware.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/network.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/permission.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/project.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/pty.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/question.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/remote.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/suggestion.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/sync.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/telemetry.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/trace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/instance/tui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/routes/ui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/server.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/shared/fence.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/shared/pty-ticket.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/shared/public-ui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/shared/tui-control.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/shared/ui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/shared/workspace-routing.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/server/workspace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/compaction.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/instruction.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/llm.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/message-error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/message-v2.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/message.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/network.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/overflow.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/processor.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/projectors-next.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/projectors.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/prompt.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/prompt/copilot-gpt-5.txt` (other) + - File needs manual review +- `packages/opencode/src/session/prompt/default.txt` (other) + - File needs manual review +- `packages/opencode/src/session/prompt/kilocode-gpt-5.5.txt` (other) + - File needs manual review +- `packages/opencode/src/session/retry.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/revert.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/run-state.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/session.sql.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/status.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/summary.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/system.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/session/todo.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/share/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/share/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/share/share-next.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/shell/shell.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/skill/discovery.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/skill/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/snapshot/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/storage/db.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/storage/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/storage/json-migration.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/storage/storage.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/sync/event.sql.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/sync/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/sync/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/temporary.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/apply_patch.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/bash.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/bash.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/codesearch.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/codesearch.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/diagnostics.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/edit.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/external-directory.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/glob.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/grep.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/grep.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/json-schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/lsp.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/lsp.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/mcp-exa.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/mcp-websearch.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/plan-exit.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/plan.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/read.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/recall.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/registry.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/repo_clone.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/repo_clone.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/repo_overview.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/repo_overview.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/shell.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/shell/id.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/shell/prompt.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/shell/shell.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/task.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/task.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/task_status.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/task_status.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/todowrite.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/tool.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/truncate.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/truncation-dir.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/warpgrep.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/webfetch.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/websearch.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/tool/websearch.txt` (other) + - File needs manual review +- `packages/opencode/src/tool/write.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/abort.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/archive.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/bom.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/color.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/filesystem.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/fn.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/keybind.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/lazy.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/local-context.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/locale.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/lock.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/log.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/media.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/named-schema-error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/network.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/process.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/repository.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/rpc.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/scrap.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/timeout.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/token.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/update-schema.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/which.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/util/wildcard.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/v2/session-entry-stepper.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/v2/session-entry.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/v2/session-event.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/v2/session.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/src/worktree/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/account/repo.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/account/service.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/acp/agent-interface.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/acp/event-subscription.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/agent/agent.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/agent/plugin-agent-regression.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/auth/auth.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/background/job.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/bus/bus-effect.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/bus/bus-integration.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/bus/bus.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/aggregate-failures.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/app-exit.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/attention.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/dialog-workspace-create.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/notifications.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/prompt-history.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/prompt-traits.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/provider-options.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/sync-fixture.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/sync-undefined-messages.test.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/cmd/tui/sync.test.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/effect-cmd-instance-als.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/error.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/github-action.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/github-remote.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/install-artifact.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/entry.body.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/footer.menu.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/footer.view.test.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/permission.shared.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/prompt.shared.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/question.shared.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/runtime.boot.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/runtime.queue.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/runtime.stdin.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/scrollback.surface.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/session-data.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/session.shared.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/stream.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/stream.transport.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/subagent-data.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/theme.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/run/variant.shared.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/editor-context-zed.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/editor-context.test.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/keybind-plugin.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/plugin-add.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/plugin-install.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/plugin-lifecycle.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/plugin-loader-pure.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/plugin-loader.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/plugin-toggle.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/prompt-submit-race.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/theme-store.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/thread.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/cli/tui/use-event.test.tsx` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/config/agent-color.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/config/config.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/config/lsp.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/config/markdown.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/config/tui.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/control-plane/adapters.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/control-plane/adaptors.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/control-plane/sse.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/control-plane/workspace.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/effect/app-runtime-logger.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/effect/config-service.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/effect/instance-state.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/effect/run-service.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/effect/runner.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/effect/runtime-flags.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fake/account.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fake/auth.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fake/npm.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fake/provider.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fake/skill.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/file/fsmonitor.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/file/index.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/file/path-traversal.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/file/ripgrep.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/file/watcher.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/filesystem/filesystem.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/agent-plugin.constants.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/agent-plugin.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/db.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/fixture.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/flag.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/flock-worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/log-init-worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/plug-worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/spreadsheet/hidden-sheet.ods` (other) + - File needs manual review +- `packages/opencode/test/fixture/spreadsheet/repeated-cells.ods` (other) + - File needs manual review +- `packages/opencode/test/fixture/tui-plugin.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/tui-runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/fixture/workspace.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/format/format.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/git/git.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/image/fixtures/picture-5mb-base64.png` (other) + - File needs manual review +- `packages/opencode/test/image/image.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/installation/installation.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/keybind.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/lib/effect.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/lib/llm-server.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/lib/websocket.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/lsp/client.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/lsp/index.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/lsp/lifecycle.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/mcp/headers.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/mcp/lifecycle.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/mcp/oauth-auto-connect.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/mcp/oauth-browser.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/memory/abort-leak-webfetch.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/memory/abort-leak.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/npm.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/patch/patch.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/permission-task.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/permission/next.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/auth-override.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/github-copilot-models.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/install-concurrency.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/install.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/loader-shared.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/meta.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/trigger.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/workspace-adapter.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/workspace-adaptor.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/plugin/xai.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/preload.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/project/instance-bootstrap.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/project/instance.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/project/migrate-global.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/project/project.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/project/vcs.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/project/worktree-remove.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/project/worktree.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/provider/amazon-bedrock.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/provider/cf-ai-gateway-e2e.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/provider/digitalocean.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/provider/gitlab-duo.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/provider/model-status.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/provider/provider.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/provider/transform.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/pty/pty-output-isolation.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/pty/pty-session.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/pty/pty-shell.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/pty/ticket.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/question/question.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/reference/reference.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/auth.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/experimental-session-list.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/global-bus.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/global-session-list.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-authorization.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-compression.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-config.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-cors-vary.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-cors.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-error-middleware.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-event.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/assertions.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/backend.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/dsl.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/environment.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/report.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/routing.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/runner.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/runtime.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-exercise/types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-experimental.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-file.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-instance-context.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-instance.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-listen.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-mcp-oauth.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-mcp.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-mdns.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-promptasync-context.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-provider.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-pty-websocket.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-pty.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-query-schema-drift.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-raw-route-auth.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-schema-error-body.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-sdk.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-session.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-sync.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-ui.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-workspace-routing.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/httpapi-workspace.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/negative-tokens-regression.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/project-init-git.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/proxy-util.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/sdk-error-shape.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/sdk-v1-smoke.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/session-actions.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/session-diff-missing-patch.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/session-list.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/session-messages.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/session-select.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/trace-attributes.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/workspace-proxy.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/workspace-routing.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/server/worktree-endpoint-repro.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/compaction.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/instruction.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/llm.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/message-v2.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/messages-pagination.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/network.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/processor-effect.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/prompt.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/retry.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/revert-compact.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/schema-decoding.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/session-entry-stepper.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/session-schema.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/session.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/shell-v2.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/snapshot-tool-race.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/structured-output-integration.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/structured-output.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/session/system.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/share/share-next.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/shell/shell.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/skill/discovery.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/skill/skill.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/snapshot/snapshot.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/storage/db.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/storage/json-migration.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/storage/storage.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/storage/workspace-time-migration.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/sync/index.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap` (other) + - File needs manual review +- `packages/opencode/test/tool/apply_patch.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/bash.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/diagnostics-filter.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/edit.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/external-directory.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/fixtures/models-api.json` (config) + - Config files may have Kilo-specific settings +- `packages/opencode/test/tool/glob.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/grep.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/lsp.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/parameters.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/question.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/read.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/recall.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/registry.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/repo_clone.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/repo_overview.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/shell.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/skill.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/task.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/task_status.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/tool-define.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/truncation.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/webfetch.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/websearch.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/tool/write.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/util/error.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/util/filesystem.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/util/glob.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/util/lock.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/util/log.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/util/module.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/util/process.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/util/wildcard.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/v2/session-message-updater.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/test/workspace/workspace-restore.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/opencode/tsconfig.json` (config) + - Config files may have Kilo-specific settings +- `packages/plugin-atomic-chat/src/cache/model-status-cache.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/cache/shared-model-status-cache.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/constants.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/plugin/auth-hook.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/plugin/chat-params-hook.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/plugin/config-hook.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/plugin/enhance-config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/plugin/event-hook.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/plugin/get-loaded-models.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/plugin/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/types/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/ui/toast-notifier.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/atomic-chat-api.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/format-model-name.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/should-probe-atomic-chat.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/validation/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/validation/safe-operations.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/validation/type-guards.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/validation/validate-config.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/validation/validate-hook-input.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/src/utils/validation/validation-result.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/test/plugin.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/test/should-probe-atomic-chat.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin-atomic-chat/tsconfig.json` (config) + - Config files may have Kilo-specific settings +- `packages/plugin/src/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin/src/tool.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/plugin/src/tui.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/script/tests/check-opencode-annotations.test.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/sdk/js/src/client.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/sdk/js/src/error-interceptor.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/sdk/js/src/gen/types.gen.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/sdk/js/src/v2/client.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/sdk/js/src/v2/gen/sdk.gen.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/sdk/js/src/v2/gen/types.gen.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/shared/src/global.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/shared/src/types.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/shared/src/util/error.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/shared/src/util/fn.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/shared/sst-env.d.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/shared/test/fixture/effect-flock-worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/shared/tsconfig.json` (config) + - Config files may have Kilo-specific settings +- `packages/storybook/.storybook/main.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/storybook/.storybook/mocks/app/context/global-sync.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/storybook/.storybook/mocks/app/context/platform.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/assets/audio/alert-01.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-02.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-03.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-04.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-05.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-06.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-07.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-08.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-09.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/alert-10.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-01.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-02.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-03.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-04.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-05.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-06.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-07.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-08.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-09.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/bip-bop-10.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-01.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-02.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-03.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-04.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-05.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-06.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-07.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-08.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-09.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-10.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-11.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/nope-12.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/staplebops-01.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/staplebops-02.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/staplebops-03.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/staplebops-04.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/staplebops-05.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/staplebops-06.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/staplebops-07.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/yup-01.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/yup-02.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/yup-03.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/yup-04.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/yup-05.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/audio/yup-06.mp3` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/abliteration-ai.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/atomic-chat.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/digitalocean.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/frogbot.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/hpc-ai.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/kiro.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/neuralwatt.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/regolo-ai.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/tencent-tokenhub.svg` (other) + - File needs manual review +- `packages/ui/src/assets/icons/provider/wafer.ai.svg` (other) + - File needs manual review +- `packages/ui/src/components/basic-tool.css` (other) + - File needs manual review +- `packages/ui/src/components/basic-tool.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/components/icon.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/components/markdown-stream.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/components/markdown.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/components/message-part.css` (other) + - File needs manual review +- `packages/ui/src/components/message-part.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/components/provider-icons/sprite.svg` (other) + - File needs manual review +- `packages/ui/src/components/provider-icons/types.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/components/select.css` (other) + - File needs manual review +- `packages/ui/src/components/select.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/components/select.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/components/session-diff.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/components/session-diff.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/components/session-turn.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/components/text-shimmer.css` (other) + - File needs manual review +- `packages/ui/src/components/text-shimmer.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/components/timeline-playground.stories.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/components/todo-panel-motion.stories.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/context/data.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/context/marked.tsx` (code) + - File has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/file-path.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/i18n/ar.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/br.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/bs.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/da.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/de.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/en.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/es.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/fr.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/it.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/ja.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/ko.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/nl.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/no.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/pl.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/ru.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/th.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/tr.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/uk.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/zh.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/i18n/zht.ts` (i18n) + - i18n file has kilocode_change markers — auto-transform skipped, needs manual review +- `packages/ui/src/kilocode/markdown-incremental-dom.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/kilocode/markdown-mermaid.css` (other) + - File needs manual review +- `packages/ui/src/kilocode/markdown-mermaid.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/kilocode/markdown-stable-blocks.test.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/kilocode/markdown-stable-blocks.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/kilocode/markdown-stream-highlight.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/pierre/index.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/pierre/kilo-diff-theme.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/pierre/worker.ts` (code) + - Code files need manual review for kilocode_change markers +- `packages/ui/src/styles/index.css` (other) + - File needs manual review +- `packages/ui/src/theme/desktop-theme.schema.json` (config) + - Config files may have Kilo-specific settings +- `packages/ui/vite.config.ts` (code) + - Code files need manual review for kilocode_change markers +- `patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch` (other) + - File needs manual review +- `patches/mammoth@1.12.0.patch` (other) + - File needs manual review +- `patches/stream-chat@9.38.0.patch` (other) + - File needs manual review +- `script/check-forbidden-strings.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/check-kilo-generated-artifacts.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/check-opencode-annotations.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/check-opencode-promise-facades.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/check-workflows.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/extract-source-links.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/jetbrains-release-pr.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/jetbrains-release-validate.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/publish.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/raw-changelog.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/setup-git.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `script/sync-versions.ts` (script) + - Script file has kilocode_change markers — auto-transform skipped, needs manual review +- `session.json` (config) + - Config files may have Kilo-specific settings +- `specs/v2/api.html` (other) + - File needs manual review +- `turbo.json` (config) + - Config files may have Kilo-specific settings + +## Recommendations + +- 720 files will be skipped (auto-removed) +- 19 i18n files will be auto-transformed +- 2067 files will keep Kilo's version +- 1655 files require manual review