mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
chore: drop unrelated review fixes
This commit is contained in:
@@ -390,8 +390,8 @@ You can also set options that apply to all models from a provider:
|
||||
| `timeout` | `number \| false` | Request timeout in milliseconds. Defaults to `300000` (5 minutes); set to `false` to disable |
|
||||
| `chunkTimeout` | `number` | Timeout in milliseconds between streamed response chunks. If no chunk arrives within this window, the request is aborted and retried. This catches silent provider dropouts where the TCP connection stays open but SSE streaming stops. Recommended: `15000`–`30000` (15–30 seconds) for providers with unreliable streaming. |
|
||||
|
||||
{% callout type="warning" title="{env:} requires trusted config; {file:} is project-confined" %}
|
||||
`{env:VAR}` references in `apiKey` (or any option) resolve **only** when the config lives in a trusted location: your global config (`~/.config/kilo`), a config passed via `KILO_CONFIG` / `KILO_CONFIG_CONTENT`, or organization/MDM-managed config. A project-level `kilo.json` / `opencode.json` committed to a repository **cannot** resolve `{env:VAR}`; if one appears anywhere in that file, Kilo drops the entire file's config and logs a warning, so unrelated settings in the same file stop applying too. This prevents a malicious repository from exfiltrating your secrets to an attacker-controlled `baseURL` just by being opened. `{file:...}` works in trusted config and still works in project config, but only for files that resolve inside the project root — references that leave it (absolute paths outside the root, `../` traversal, and symlink escapes) are rejected. Keep provider credentials in your global config.
|
||||
{% callout type="warning" title="{env:} / {file:} only resolve in trusted config" %}
|
||||
`{env:VAR}` and `{file:...}` references in `apiKey` (or any option) are resolved **only** when the config lives in a trusted location: your global config (`~/.config/kilo`), a config passed via `KILO_CONFIG` / `KILO_CONFIG_CONTENT`, or organization/MDM-managed config. A project-level `kilo.json` / `opencode.json` committed to a repository **cannot** resolve `{env:VAR}` — the reference is ignored and a warning is logged, so a provider configured this way in a repo will not authenticate. This prevents a malicious repository from exfiltrating your secrets to an attacker-controlled `baseURL` just by being opened. `{file:...}` still works in project config, but only for files that resolve inside the project root — references that leave it (absolute paths outside the root, `../` traversal, and symlink escapes) are rejected. Keep provider credentials in your global config.
|
||||
{% /callout %}
|
||||
|
||||
## Filtering Available Models
|
||||
|
||||
@@ -465,8 +465,8 @@ Use `{env:VARIABLE_NAME}` syntax in config files to reference environment variab
|
||||
}
|
||||
```
|
||||
|
||||
{% callout type="warning" title="{env:} requires trusted config; {file:} is project-confined" %}
|
||||
`{env:VAR}` references resolve **only** in trusted config: your global config (`~/.config/kilo`), a config passed via `KILO_CONFIG` / `KILO_CONFIG_CONTENT`, or organization/MDM-managed config. A project-level `kilo.json` / `opencode.json` committed to a repository **cannot** use `{env:VAR}`; if one appears anywhere in that file, Kilo drops the entire file's config and logs a warning, so unrelated settings in the same file stop applying too. This prevents a malicious repository from exfiltrating your secrets to an attacker-controlled `baseURL` simply by being opened. `{file:...}` works in trusted config and still works in project config, but only for files that resolve inside the project root — references that leave it (absolute paths outside the root, `../` traversal, and symlink escapes) are rejected.
|
||||
{% callout type="warning" title="Only works in trusted config" %}
|
||||
`{env:VAR}` (and `{file:...}`) references are resolved **only** in trusted config: your global config (`~/.config/kilo`), a config passed via `KILO_CONFIG` / `KILO_CONFIG_CONTENT`, or organization/MDM-managed config. A project-level `kilo.json` / `opencode.json` committed to a repository **cannot** use `{env:VAR}` — the reference is ignored and a warning is logged. This prevents a malicious repository from exfiltrating your secrets to an attacker-controlled `baseURL` simply by being opened. `{file:...}` still works in project config, but only for files that resolve inside the project root — references that leave it (absolute paths outside the root, `../` traversal, and symlink escapes) are rejected.
|
||||
{% /callout %}
|
||||
|
||||
For full details on all configuration options including compaction, file watchers, plugins, and experimental features, see the [OpenCode Config documentation](https://opencode.ai/docs/config).
|
||||
|
||||
@@ -47,19 +47,4 @@ describe("splitConfigByScope", () => {
|
||||
})
|
||||
expect(split.project).toEqual({})
|
||||
})
|
||||
|
||||
it("writes sandbox writable paths to global config", () => {
|
||||
const split = splitConfigByScope({
|
||||
experimental: {
|
||||
sandbox_writable_paths: ["/tmp/kilo"],
|
||||
},
|
||||
})
|
||||
|
||||
expect(split.global).toEqual({
|
||||
experimental: {
|
||||
sandbox_writable_paths: ["/tmp/kilo"],
|
||||
},
|
||||
})
|
||||
expect(split.project).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { cleanupSpeechCapture } from "../../webview-ui/src/components/speech-to-text/speech-cleanup"
|
||||
|
||||
describe("speech-to-text button", () => {
|
||||
it("cancels active capture on component cleanup", () => {
|
||||
let cancels = 0
|
||||
|
||||
cleanupSpeechCapture({
|
||||
active: () => true,
|
||||
cancel: () => {
|
||||
cancels += 1
|
||||
},
|
||||
})
|
||||
|
||||
expect(cancels).toBe(1)
|
||||
})
|
||||
|
||||
it("does not cancel idle capture on component cleanup", () => {
|
||||
let cancels = 0
|
||||
|
||||
cleanupSpeechCapture({
|
||||
active: () => false,
|
||||
cancel: () => {
|
||||
cancels += 1
|
||||
},
|
||||
})
|
||||
|
||||
expect(cancels).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -1394,7 +1394,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<WandSparkles size={16} class={enhancing() ? "enhance-spinner" : ""} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Show when={canUseSpeech() || speech.active()}>
|
||||
<Show when={canUseSpeech()}>
|
||||
<SpeechToTextButton speech={speech} disabled={isDisabled()} start={startSpeech} label={language.t} />
|
||||
</Show>
|
||||
<Show
|
||||
|
||||
+3
-2
@@ -3,7 +3,6 @@ import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import { Spinner } from "@kilocode/kilo-ui/spinner"
|
||||
import { onCleanup, type Component } from "solid-js"
|
||||
import type { SpeechToText } from "./useSpeechToText"
|
||||
import { cleanupSpeechCapture } from "./speech-cleanup"
|
||||
|
||||
type Props = {
|
||||
speech: SpeechToText
|
||||
@@ -42,7 +41,9 @@ export const SpeechToTextButton: Component<Props> = (props) => {
|
||||
props.start()
|
||||
}
|
||||
|
||||
onCleanup(() => cleanupSpeechCapture(props.speech))
|
||||
onCleanup(() => {
|
||||
if (props.speech.active()) props.speech.cancel()
|
||||
})
|
||||
|
||||
return (
|
||||
<Tooltip value={label()} placement="top">
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { SpeechToText } from "./useSpeechToText"
|
||||
|
||||
export function cleanupSpeechCapture(speech: Pick<SpeechToText, "active" | "cancel">) {
|
||||
if (speech.active()) speech.cancel()
|
||||
}
|
||||
@@ -1738,8 +1738,11 @@ function StepFinishPart(props: { last: boolean; part: StepFinishPart; message: A
|
||||
const { theme } = useTheme()
|
||||
const info = useContext(RoutedModelMeta.Context)
|
||||
const routed = createMemo(() => {
|
||||
if (props.message.providerID !== "kilo") return undefined
|
||||
if (!props.message.modelID.startsWith("kilo-auto/")) return undefined
|
||||
const model = props.part.model
|
||||
if (!RoutedModelMeta.routed(model, props.message)) return undefined
|
||||
if (!model) return undefined
|
||||
if (model.providerID === props.message.providerID && model.modelID === props.message.modelID) return undefined
|
||||
return RoutedModelMeta.label(ctx.providers(), model)
|
||||
})
|
||||
const consumed = createMemo(() => info().consumed.has(props.part.id))
|
||||
|
||||
@@ -299,7 +299,7 @@ function resolveMotion(text: string, pos: number, key: string, count: number, aw
|
||||
case "$": {
|
||||
let t = pos
|
||||
for (let i = 0; i < reps; i++) t = lineEnd(text, lineEnd(text, t) + (i === 0 ? 0 : 1))
|
||||
return { target: Math.max(lineStart(text, t), lineEnd(text, t) - 1), inclusive: true, linewise: false }
|
||||
return { target: lineEnd(text, t), inclusive: true, linewise: false }
|
||||
}
|
||||
case "G": {
|
||||
let target = 0
|
||||
|
||||
@@ -39,10 +39,10 @@ export namespace RoutedModelMeta {
|
||||
return KiloRoutedModel.displayName(text)
|
||||
}
|
||||
|
||||
export function routed(model: StepFinishPart["model"], message: Message) {
|
||||
function routed(model: StepFinishPart["model"], message: Message) {
|
||||
if (!model) return undefined
|
||||
if (message.providerID !== "kilo") return undefined
|
||||
if (!KiloRoutedModel.routedSelection(message.modelID)) return undefined
|
||||
if (!message.modelID.startsWith("kilo-auto/")) return undefined
|
||||
if (model.providerID === message.providerID && model.modelID === message.modelID) return undefined
|
||||
return model
|
||||
}
|
||||
|
||||
@@ -47,15 +47,10 @@ export namespace KiloRoutedModel {
|
||||
input: { providerID: ProviderID; modelID: string; selected?: string },
|
||||
) {
|
||||
if (input.providerID !== ProviderID.kilo) return undefined
|
||||
if (!routedSelection(input.modelID)) return undefined
|
||||
if (!input.modelID.startsWith("kilo-auto/") && !input.modelID.includes("fable")) return undefined
|
||||
const model = read(meta, input.providerID)
|
||||
if (!model) return undefined
|
||||
if (model.modelID === input.modelID || model.modelID === input.selected) return undefined
|
||||
return model
|
||||
}
|
||||
|
||||
export function routedSelection(modelID: string) {
|
||||
const id = modelID.trim()
|
||||
return id.startsWith("kilo-auto/") || /(?:^|[/.])claude-fable(?:[-.:/]|$)/.test(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,28 +199,6 @@ describe("vim edits", () => {
|
||||
expect(doc.text).toBe("bar baz")
|
||||
})
|
||||
|
||||
test("d$ deletes line content without consuming the newline", () => {
|
||||
const doc = new MockDoc("abc\ndef", 0)
|
||||
const state = createVimState("normal")
|
||||
feed(doc, state, "d$")
|
||||
expect(doc.text).toBe("\ndef")
|
||||
})
|
||||
|
||||
test("c$ changes line content without consuming the newline", () => {
|
||||
const doc = new MockDoc("abc\ndef", 0)
|
||||
const state = createVimState("normal")
|
||||
feed(doc, state, "c$")
|
||||
expect(doc.text).toBe("\ndef")
|
||||
expect(state.mode).toBe("insert")
|
||||
})
|
||||
|
||||
test("y$ yanks line content without the newline", () => {
|
||||
const doc = new MockDoc("abc\ndef", 0)
|
||||
const state = createVimState("normal")
|
||||
feed(doc, state, "y$")
|
||||
expect(state.register).toEqual({ text: "abc", linewise: false })
|
||||
})
|
||||
|
||||
test("dd deletes the current line", () => {
|
||||
const doc = new MockDoc("one\ntwo\nthree", 4)
|
||||
const state = createVimState("normal")
|
||||
|
||||
@@ -243,51 +243,6 @@ describe("kilocode indexing config", () => {
|
||||
})
|
||||
|
||||
describe("kilocode sandbox writable paths config", () => {
|
||||
test("honors sandbox_writable_paths from global config", async () => {
|
||||
await using globalTmp = await tmpdir()
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
const prev = Global.Path.config
|
||||
;(Global.Path as { config: string }).config = globalTmp.path
|
||||
await clear()
|
||||
await disposeAllInstances()
|
||||
|
||||
try {
|
||||
await writeConfig(globalTmp.path, {
|
||||
$schema: "https://app.kilo.ai/config.json",
|
||||
experimental: { sandbox_writable_paths: ["/tmp/global"] },
|
||||
})
|
||||
|
||||
await provideTestInstance({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await load()
|
||||
expect(config.experimental?.sandbox_writable_paths).toEqual(["/tmp/global"])
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
;(Global.Path as { config: string }).config = prev
|
||||
await clear()
|
||||
await disposeAllInstances()
|
||||
}
|
||||
})
|
||||
|
||||
test("drops sandbox_writable_paths from project config", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
await writeConfig(tmp.path, {
|
||||
experimental: { sandbox_writable_paths: ["/tmp/project"] },
|
||||
})
|
||||
|
||||
await provideTestInstance({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await load()
|
||||
expect(config.experimental?.sandbox_writable_paths).toBeUndefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("honors sandbox_writable_paths from global config only, ignoring project config", async () => {
|
||||
await using globalTmp = await tmpdir()
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
@@ -103,7 +103,7 @@ describe("session routed model", () => {
|
||||
expect(KiloRoutedModel.displayName("o3")).toBe("o3")
|
||||
})
|
||||
|
||||
test("shows compact labels only for routed Kilo selections", () => {
|
||||
test("shows compact labels only for Kilo auto selections", () => {
|
||||
const model = { providerID: "openai", modelID: "gpt-5.5" }
|
||||
const parts = [reason, finish(model)]
|
||||
|
||||
@@ -115,13 +115,6 @@ describe("session routed model", () => {
|
||||
expect(routed.footer).toBe("gpt-5.5")
|
||||
expect(routed.consumed.has("finish")).toBe(true)
|
||||
|
||||
const fable = RoutedModelMeta.info(undefined, parts, false, {
|
||||
providerID: "kilo",
|
||||
modelID: "anthropic/claude-fable-5",
|
||||
})
|
||||
expect(fable.labels.get("reasoning")).toBe("gpt-5.5")
|
||||
expect(fable.footer).toBe("gpt-5.5")
|
||||
|
||||
const explicit = RoutedModelMeta.info(undefined, parts, false, {
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.5",
|
||||
@@ -176,7 +169,7 @@ describe("session routed model", () => {
|
||||
expect(routed.consumed.has("last")).toBe(false)
|
||||
})
|
||||
|
||||
test("reads routed model only for selected Kilo routed models", () => {
|
||||
test("reads routed model only for selected Kilo auto models", () => {
|
||||
const meta = { kilocode: { routedModelID: "openai/gpt-5.5-20260423" } }
|
||||
|
||||
expect(
|
||||
@@ -189,22 +182,6 @@ describe("session routed model", () => {
|
||||
modelID: ModelID.make("openai/gpt-5.5-20260423"),
|
||||
})
|
||||
|
||||
expect(
|
||||
KiloRoutedModel.readAuto(meta, {
|
||||
providerID: ProviderID.kilo,
|
||||
modelID: "anthropic.claude-fable-5",
|
||||
}),
|
||||
).toEqual({
|
||||
providerID: ProviderID.kilo,
|
||||
modelID: ModelID.make("openai/gpt-5.5-20260423"),
|
||||
})
|
||||
|
||||
expect(
|
||||
KiloRoutedModel.readAuto(meta, {
|
||||
providerID: ProviderID.kilo,
|
||||
modelID: "openai/affable-model",
|
||||
}),
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
KiloRoutedModel.readAuto(meta, {
|
||||
providerID: ProviderID.kilo,
|
||||
|
||||
Reference in New Issue
Block a user