mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
Merge branch 'main' into fix-jetbrains-eager-watchers
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show file names before their containing folders in JetBrains `@file` suggestions.
|
||||
@@ -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.
|
||||
@@ -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(
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+10
@@ -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",
|
||||
|
||||
@@ -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 ?? []
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -33415,6 +33415,9 @@
|
||||
"hide_prompt_training_models": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"privacy_mode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user