Merge branch 'main' into fix-jetbrains-eager-watchers

This commit is contained in:
Kirill Kalishev
2026-08-05 10:39:50 -04:00
committed by GitHub
14 changed files with 98 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show file names before their containing folders in JetBrains `@file` suggestions.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Add a privacy mode that blurs PII in the TUI (personal balance, Kilo Pass usage, etc.) and requires confirmation before `/profile` reveals email, name, balance, and team. Toggle with the new `/privacy` command or by setting `privacy_mode` in `kilo.json`. The `kilo profile` CLI command is unaffected.
+4
View File
@@ -129,6 +129,10 @@ export const Info = Schema.Struct({
hide_prompt_training_models: Schema.optional(Schema.Boolean).annotate({
description: "Hide Kilo Gateway models that may train on your prompts from model listings",
}),
privacy_mode: Schema.optional(Schema.Boolean).annotate({
description:
"Blur personally identifiable information (account email, balance, team name, etc.) in the TUI and require confirmation before showing profile details",
}),
sandbox: Schema.optional(
Schema.Struct({
enabled: Schema.optional(
@@ -252,7 +252,7 @@ class KiloPromptCompletionProvider(
PrioritizedLookupElement.withGrouping(PrioritizedLookupElement.withPriority(element, 100.0), 100)
private fun file(file: WorkspaceFileDto): LookupElement = LookupElementBuilder.create(file.path)
.withPresentableText("@${file.path}")
.withPresentableText("@${file.name}")
.withTailText(parent(file.path), true)
.withIcon(icon(file))
.withLookupString(file.name)
@@ -211,6 +211,16 @@ class KiloPromptCompletionProviderTest : BasePlatformTestCase() {
assertSame(AllIcons.Nodes.Folder, icon("src"))
}
fun `test mention completion renders filename before parent path`() {
rpc.searchResult = FileSearchResultDto(files = listOf(file("src/foo/Bar.kt")))
complete("@bar<caret>")
val view = LookupElementPresentation().also { item("src/foo/Bar.kt").renderElement(it) }
assertEquals("@Bar.kt", view.itemText)
assertEquals(" src/foo", view.tailText)
}
fun `test highlights known slash command at start`() {
assertEquals(
listOf(KiloPromptCompletionProvider.Highlight(0, 4, KiloPromptCompletionProvider.HighlightKind.COMMAND)),
@@ -88,6 +88,7 @@ export namespace KilocodeConfigOverlay {
["model"],
["small_model"],
["hide_prompt_training_models"],
["privacy_mode"],
["default_agent"],
["snapshot"],
["share"],
@@ -11,6 +11,8 @@ import { useRoute } from "@tui/context/route"
import { useDialog } from "@tui/ui/dialog"
import { useToast } from "@tui/ui/toast"
import { DialogAlert } from "@tui/ui/dialog-alert"
import { DialogConfirm } from "@tui/ui/dialog-confirm"
import { reconcile } from "solid-js/store"
import type { Organization } from "@kilocode/kilo-gateway"
import type { ClawStatus } from "./claw/types.js"
import { DialogKiloTeamSelect } from "./components/dialog-kilo-team-select.js"
@@ -137,6 +139,15 @@ export function registerKiloCommands(useSDK: () => UseSDK) {
hidden: !isKiloConnected(),
run: async () => {
try {
if (sync.data.globalConfig.privacy_mode === true) {
const confirmed = await DialogConfirm.show(
dialog,
"Privacy Mode Enabled",
"Privacy mode is on. Revealing your profile will display your email, name, balance, and team on screen.",
)
if (confirmed !== true) return
}
// Fetch profile and balance using server endpoint
const response = await sdk.client.kilo.profile()
@@ -176,6 +187,39 @@ export function registerKiloCommands(useSDK: () => UseSDK) {
]
: []),
// /privacy command
{
name: "kilo.privacy",
get title() {
return sync.data.globalConfig.privacy_mode === true ? "Disable privacy mode" : "Enable privacy mode"
},
desc: "Blur PII (balance, email, etc.) and confirm before showing profile",
category: "Kilo",
slashName: "privacy",
run: async () => {
const next = sync.data.globalConfig.privacy_mode !== true
const response = await sdk.client.config.overlayUpdate({
scope: "global",
set: { privacy_mode: next },
})
if (response.error) {
const status = response.response?.status ?? "?"
toast.show({ message: `Failed to update privacy mode (${status})`, variant: "error" })
return
}
const [cfg, global] = await Promise.all([
sdk.client.config.get({}),
sdk.client.global.config.get({}),
])
if (cfg.data) sync.set("config", reconcile(cfg.data))
if (global.data) sync.set("globalConfig", reconcile(global.data))
toast.show({
message: next ? "Privacy mode enabled" : "Privacy mode disabled",
variant: "success",
})
},
},
// /teams command
{
name: "kilo.teams",
+1
View File
@@ -0,0 +1 @@
export const REDACTED_BALANCE = "•••"
@@ -5,6 +5,7 @@ import * as Log from "@opencode-ai/core/util/log"
import type { KiloPassState } from "@kilocode/kilo-gateway"
import type { Message } from "@kilocode/sdk/v2"
import { onBalanceRefresh } from "../balance-refresh"
import { REDACTED_BALANCE } from "../pii"
const id = "internal:kilo-sidebar-footer"
const TEAM_POLL_MS = 5 * 60_000
@@ -37,8 +38,9 @@ export function scope(org: string | null | undefined, list?: readonly { id: stri
}
}
export function creditLabel(value: ReturnType<typeof scope>) {
export function creditLabel(value: ReturnType<typeof scope>, masked = false) {
if (value.kind === "Personal") return "Personal credits"
if (masked) return "Team credits"
return value.name ? `${value.name} team` : "Team credits"
}
@@ -98,6 +100,9 @@ function View(props: { api: TuiPluginApi }) {
name: list.at(-1) ?? "",
}
})
const privacyMode = createMemo(() => props.api.state.globalConfig.privacy_mode === true)
const balanceText = createMemo(() => (privacyMode() ? REDACTED_BALANCE : null))
const mutedColor = createMemo(() => (privacyMode() ? theme().textMuted : tone()))
const refresh = () => {
const id = ++seq
// Cancel any prior request and time this one out — the client path has no fetch timeout,
@@ -167,19 +172,20 @@ function View(props: { api: TuiPluginApi }) {
{(() => {
const balance = data().balance
if (balance === undefined) return null
const masked = balanceText()
return (
<box flexDirection="row" justifyContent="space-between">
<box flexDirection="row" gap={1}>
<text fg={tone()}></text>
<text fg={mutedColor()}></text>
<text fg={theme().text}>
<b>{creditLabel(data().scope)}</b>
<b>{creditLabel(data().scope, privacyMode())}</b>
</text>
</box>
<text fg={tone()}>{format(balance)}</text>
<text fg={mutedColor()}>{masked ?? format(balance)}</text>
</box>
)
})()}
<Show when={data().scope.kind === "Personal" ? data().pass : null}>
<Show when={privacyMode() ? null : data().scope.kind === "Personal" ? data().pass : null}>
{(pass) => (
<box gap={0}>
<box flexDirection="row" justifyContent="space-between" gap={1}>
@@ -97,6 +97,7 @@ type Opts = {
state?: {
ready?: HostPluginApi["state"]["ready"]
config?: HostPluginApi["state"]["config"]
globalConfig?: HostPluginApi["state"]["globalConfig"] // kilocode_change
provider?: HostPluginApi["state"]["provider"]
path?: HostPluginApi["state"]["path"]
vcs?: HostPluginApi["state"]["vcs"]
@@ -303,6 +304,11 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
get config() {
return opts.state?.config ?? {}
},
// kilocode_change start
get globalConfig() {
return opts.state?.globalConfig ?? {}
},
// kilocode_change end
get provider() {
return opts.state?.provider ?? []
},
+1
View File
@@ -376,6 +376,7 @@ export type TuiKV = {
export type TuiState = {
readonly ready: boolean
readonly config: SdkConfig
readonly globalConfig: SdkConfig
readonly provider: ReadonlyArray<Provider>
readonly path: {
state: string
+1
View File
@@ -2555,6 +2555,7 @@ export type Config = {
terminal_command_display?: "expanded" | "collapsed"
code_edit_display?: "expanded" | "collapsed"
hide_prompt_training_models?: boolean
privacy_mode?: boolean
/**
* Sandbox configuration for agent tools
*/
+3
View File
@@ -33415,6 +33415,9 @@
"hide_prompt_training_models": {
"type": "boolean"
},
"privacy_mode": {
"type": "boolean"
},
"sandbox": {
"type": "object",
"properties": {
+5
View File
@@ -107,6 +107,11 @@ function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
get config() {
return sync.data.config
},
// kilocode_change start
get globalConfig() {
return sync.data.globalConfig
},
// kilocode_change end
get provider() {
return sync.data.provider
},