mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-30 19:32:39 +08:00
Granular bash permission rules (#7091)
* fix: renamed pattern arrays used for permission rules * fix: rename savePatternRules to saveAlwaysRules * feat: generate hierarchical always patterns in bash tool * feat: validate saveAlwaysRules against always array only * feat: pass always field through extension bridge to webview * feat: show hierarchical always patterns in PermissionDock dropdown * fix: rename pattern to rule in PermissionDock translations, styles, and data-slots * fix(vscode): correct auto-run comment in PermissionDock * feat(kilo-vscode): strip trailing wildcard from permission rule labels * fix: deduplicate permission rules and strip wildcards from display labels * refactor: move hierarchy to metadata.rules, restore always to arity prefix * feat: pass always field to permission dock for non-bash tool rules * fix: correct bash test assertion to check metadata.rules and fix indentation * fix: hide permission rules dropdown for non-bash tools * fix: align always-rules route descriptions, whitespace, and restore multi-command tests
This commit is contained in:
@@ -354,8 +354,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
message.permissionId,
|
||||
message.sessionID,
|
||||
message.response,
|
||||
message.approvedPatterns,
|
||||
message.deniedPatterns,
|
||||
message.approvedAlways,
|
||||
message.deniedAlways,
|
||||
)
|
||||
break
|
||||
case "createSession":
|
||||
@@ -1654,14 +1654,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
/**
|
||||
* Handle permission response from the webview.
|
||||
* Calls savePatternRules first (if any), then reply — sequentially to avoid races.
|
||||
* Calls saveAlwaysRules first (if any), then reply — sequentially to avoid races.
|
||||
*/
|
||||
private async handlePermissionResponse(
|
||||
permissionId: string,
|
||||
sessionID: string,
|
||||
response: "once" | "always" | "reject",
|
||||
approvedPatterns: string[],
|
||||
deniedPatterns: string[],
|
||||
approvedAlways: string[],
|
||||
deniedAlways: string[],
|
||||
): Promise<void> {
|
||||
if (!this.client) {
|
||||
this.postMessage({ type: "permissionError", permissionID: permissionId })
|
||||
@@ -1679,13 +1679,13 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
const workspaceDir = this.getWorkspaceDirectory(targetSessionID)
|
||||
|
||||
// Save per-pattern rules before replying (reply deletes the pending request)
|
||||
if (approvedPatterns.length > 0 || deniedPatterns.length > 0) {
|
||||
await this.client.permission.savePatternRules(
|
||||
if (approvedAlways.length > 0 || deniedAlways.length > 0) {
|
||||
await this.client.permission.saveAlwaysRules(
|
||||
{
|
||||
requestID: permissionId,
|
||||
directory: workspaceDir,
|
||||
approvedPatterns,
|
||||
deniedPatterns,
|
||||
approvedAlways,
|
||||
deniedAlways,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
)
|
||||
|
||||
@@ -76,10 +76,10 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
onCleanup(() => document.removeEventListener("keydown", handler))
|
||||
})
|
||||
|
||||
const decide = (response: "once" | "always" | "reject", approvedPatterns: string[], deniedPatterns: string[]) => {
|
||||
const decide = (response: "once" | "always" | "reject", approvedAlways: string[], deniedAlways: string[]) => {
|
||||
const perm = permissionRequest()
|
||||
if (!perm || session.respondingPermissions().has(perm.id)) return
|
||||
session.respondToPermission(perm.id, response, approvedPatterns, deniedPatterns)
|
||||
session.respondToPermission(perm.id, response, approvedAlways, deniedAlways)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* Displays permission requests from the AI assistant in the dock above the prompt input.
|
||||
* Uses kilo-ui's DockPrompt component for proper surface styling.
|
||||
*
|
||||
* Per-pattern toggles allow users to approve/deny individual patterns for future requests.
|
||||
* Per-rule toggles allow users to approve/deny individual permission rules for future requests.
|
||||
* The command buttons (Deny / Allow Always / Allow Once) control the current command.
|
||||
* When all patterns are toggled ✓, the command auto-runs.
|
||||
* When all rules are toggled ✓, the command auto-runs.
|
||||
*/
|
||||
|
||||
import { Component, For, Show, createSignal } from "solid-js"
|
||||
@@ -17,74 +17,78 @@ import { useSession } from "../../context/session"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import type { PermissionRequest } from "../../types/messages"
|
||||
|
||||
type PatternDecision = "approved" | "denied" | "pending"
|
||||
type RuleDecision = "approved" | "denied" | "pending"
|
||||
|
||||
let permissionPatternsExpandedPreference = false
|
||||
let rulesExpandedPreference = false
|
||||
|
||||
export const PermissionDock: Component<{
|
||||
request: PermissionRequest
|
||||
responding: boolean
|
||||
onDecide: (response: "once" | "always" | "reject", approvedPatterns: string[], deniedPatterns: string[]) => void
|
||||
onDecide: (response: "once" | "always" | "reject", approvedAlways: string[], deniedAlways: string[]) => void
|
||||
}> = (props) => {
|
||||
const session = useSession()
|
||||
const language = useLanguage()
|
||||
|
||||
const fromChild = () => props.request.sessionID !== session.currentSessionID()
|
||||
const patterns = () => props.request.patterns ?? []
|
||||
// Bash sends fine-grained rules via metadata.rules; other tools have no dropdown.
|
||||
const rules = () => props.request.args?.rules ?? []
|
||||
// Rules like "git *" or "git log *" — strip the trailing wildcard for display.
|
||||
// A bare "*" (global wildcard) becomes empty so only the tool name shows.
|
||||
const label = (rule: string) => (rule === "*" ? "" : rule.replace(/ \*$/, ""))
|
||||
const command = () => {
|
||||
const cmd = props.request.args?.command
|
||||
return typeof cmd === "string" ? cmd : undefined
|
||||
}
|
||||
|
||||
const [decisions, setDecisions] = createSignal<Record<number, PatternDecision>>({})
|
||||
const [patternsExpanded, setPatternsExpanded] = createSignal(permissionPatternsExpandedPreference)
|
||||
const [decisions, setDecisions] = createSignal<Record<number, RuleDecision>>({})
|
||||
const [expanded, setExpanded] = createSignal(rulesExpandedPreference)
|
||||
|
||||
const hasDeniedPatterns = () => Object.values(decisions()).some((d) => d === "denied")
|
||||
const hasPatterns = () => patterns().length > 0
|
||||
const hasDenied = () => Object.values(decisions()).some((d) => d === "denied")
|
||||
const hasRules = () => rules().length > 0
|
||||
|
||||
const togglePatternsExpanded = () => {
|
||||
const next = !patternsExpanded()
|
||||
permissionPatternsExpandedPreference = next
|
||||
setPatternsExpanded(next)
|
||||
const toggleExpanded = () => {
|
||||
const next = !expanded()
|
||||
rulesExpandedPreference = next
|
||||
setExpanded(next)
|
||||
}
|
||||
|
||||
const collectPatterns = () => {
|
||||
const all = patterns()
|
||||
const collectRules = () => {
|
||||
const all = rules()
|
||||
const approved: string[] = []
|
||||
const denied: string[] = []
|
||||
for (const [i, d] of Object.entries(decisions())) {
|
||||
const pattern = all[Number(i)]
|
||||
if (!pattern) continue
|
||||
if (d === "approved") approved.push(pattern)
|
||||
else if (d === "denied") denied.push(pattern)
|
||||
const rule = all[Number(i)]
|
||||
if (!rule) continue
|
||||
if (d === "approved") approved.push(rule)
|
||||
else if (d === "denied") denied.push(rule)
|
||||
}
|
||||
return { approved, denied }
|
||||
}
|
||||
|
||||
const togglePattern = (index: number, decision: PatternDecision) => {
|
||||
const toggleRule = (index: number, decision: RuleDecision) => {
|
||||
const current = decisions()[index]
|
||||
const next = current === decision ? "pending" : decision
|
||||
const updated = { ...decisions(), [index]: next }
|
||||
setDecisions(updated)
|
||||
|
||||
const total = patterns().length
|
||||
const total = rules().length
|
||||
const count = Object.values(updated).filter((d) => d === "approved").length
|
||||
if (count === total && total > 0) {
|
||||
props.onDecide("once", [...patterns()], [])
|
||||
props.onDecide("once", [...rules()], [])
|
||||
}
|
||||
}
|
||||
|
||||
const decision = (index: number): PatternDecision => decisions()[index] ?? "pending"
|
||||
const decision = (index: number): RuleDecision => decisions()[index] ?? "pending"
|
||||
|
||||
const approveTooltip = (index: number) =>
|
||||
decision(index) === "approved"
|
||||
? language.t("ui.permission.pattern.removeFromAllowed")
|
||||
: language.t("ui.permission.pattern.addToAllowed")
|
||||
? language.t("ui.permission.rule.removeFromAllowed")
|
||||
: language.t("ui.permission.rule.addToAllowed")
|
||||
|
||||
const denyTooltip = (index: number) =>
|
||||
decision(index) === "denied"
|
||||
? language.t("ui.permission.pattern.removeFromDenied")
|
||||
: language.t("ui.permission.pattern.addToDenied")
|
||||
? language.t("ui.permission.rule.removeFromDenied")
|
||||
: language.t("ui.permission.rule.addToDenied")
|
||||
|
||||
const toolDescription = () => {
|
||||
const key = `settings.permissions.tool.${props.request.toolName}.description`
|
||||
@@ -110,35 +114,35 @@ export const PermissionDock: Component<{
|
||||
</div>
|
||||
}
|
||||
footer={
|
||||
<Show when={hasPatterns()}>
|
||||
<div data-slot="permission-patterns-section">
|
||||
<Show when={hasRules()}>
|
||||
<div data-slot="permission-rules-section">
|
||||
<button
|
||||
type="button"
|
||||
data-slot="permission-patterns-header"
|
||||
data-open={patternsExpanded() ? "" : undefined}
|
||||
onClick={togglePatternsExpanded}
|
||||
aria-expanded={patternsExpanded()}
|
||||
data-slot="permission-rules-header"
|
||||
data-open={expanded() ? "" : undefined}
|
||||
onClick={toggleExpanded}
|
||||
aria-expanded={expanded()}
|
||||
>
|
||||
<span data-slot="permission-patterns-header-chevron" data-open={patternsExpanded() ? "" : undefined}>
|
||||
<span data-slot="permission-rules-header-chevron" data-open={expanded() ? "" : undefined}>
|
||||
<Icon name="chevron-down" size="small" />
|
||||
</span>
|
||||
<span data-slot="permission-patterns-header-title">{language.t("ui.permission.permissionRules")}</span>
|
||||
<span data-slot="permission-rules-header-title">{language.t("ui.permission.permissionRules")}</span>
|
||||
</button>
|
||||
|
||||
<div data-slot="permission-patterns-collapse" data-open={patternsExpanded() ? "" : undefined}>
|
||||
<div data-slot="permission-patterns-collapse-inner">
|
||||
<div data-slot="permission-patterns">
|
||||
<For each={patterns()}>
|
||||
{(pattern, index) => (
|
||||
<div data-slot="permission-pattern-row" data-decision={decision(index())}>
|
||||
<div data-slot="permission-pattern-actions">
|
||||
<div data-slot="permission-rules-collapse" data-open={expanded() ? "" : undefined}>
|
||||
<div data-slot="permission-rules-collapse-inner">
|
||||
<div data-slot="permission-rules">
|
||||
<For each={rules()}>
|
||||
{(rule, index) => (
|
||||
<div data-slot="permission-rule-row" data-decision={decision(index())}>
|
||||
<div data-slot="permission-rule-actions">
|
||||
<Tooltip value={approveTooltip(index())} placement="top">
|
||||
<button
|
||||
data-slot="permission-pattern-toggle"
|
||||
data-slot="permission-rule-toggle"
|
||||
data-variant="approve"
|
||||
data-active={decision(index()) === "approved" ? "" : undefined}
|
||||
disabled={props.responding}
|
||||
onClick={() => togglePattern(index(), "approved")}
|
||||
onClick={() => toggleRule(index(), "approved")}
|
||||
aria-label={approveTooltip(index())}
|
||||
>
|
||||
<Icon name="check-small" size="small" />
|
||||
@@ -146,19 +150,19 @@ export const PermissionDock: Component<{
|
||||
</Tooltip>
|
||||
<Tooltip value={denyTooltip(index())} placement="top">
|
||||
<button
|
||||
data-slot="permission-pattern-toggle"
|
||||
data-slot="permission-rule-toggle"
|
||||
data-variant="deny"
|
||||
data-active={decision(index()) === "denied" ? "" : undefined}
|
||||
disabled={props.responding}
|
||||
onClick={() => togglePattern(index(), "denied")}
|
||||
onClick={() => toggleRule(index(), "denied")}
|
||||
aria-label={denyTooltip(index())}
|
||||
>
|
||||
<Icon name="close-small" size="small" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span data-slot="permission-pattern-type">{props.request.toolName}</span>
|
||||
<code data-slot="permission-pattern">{pattern}</code>
|
||||
<span data-slot="permission-rule-type">{props.request.toolName}</span>
|
||||
<code data-slot="permission-rule">{label(rule)}</code>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
@@ -181,7 +185,7 @@ export const PermissionDock: Component<{
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const { approved, denied } = collectPatterns()
|
||||
const { approved, denied } = collectRules()
|
||||
props.onDecide("once", approved, denied)
|
||||
}}
|
||||
disabled={props.responding}
|
||||
@@ -192,10 +196,10 @@ export const PermissionDock: Component<{
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const { approved, denied } = collectPatterns()
|
||||
const { approved, denied } = collectRules()
|
||||
props.onDecide("always", approved, denied)
|
||||
}}
|
||||
disabled={props.responding || hasDeniedPatterns()}
|
||||
disabled={props.responding || hasDenied()}
|
||||
>
|
||||
{language.t("ui.permission.allowAlways")}
|
||||
</Button>
|
||||
@@ -203,7 +207,7 @@ export const PermissionDock: Component<{
|
||||
variant="ghost"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const { approved, denied } = collectPatterns()
|
||||
const { approved, denied } = collectRules()
|
||||
props.onDecide("reject", approved, denied)
|
||||
}}
|
||||
disabled={props.responding}
|
||||
|
||||
@@ -142,8 +142,8 @@ interface SessionContextValue {
|
||||
respondToPermission: (
|
||||
permissionId: string,
|
||||
response: "once" | "always" | "reject",
|
||||
approvedPatterns: string[],
|
||||
deniedPatterns: string[],
|
||||
approvedAlways: string[],
|
||||
deniedAlways: string[],
|
||||
) => void
|
||||
replyToQuestion: (requestID: string, answers: string[][]) => void
|
||||
rejectQuestion: (requestID: string) => void
|
||||
@@ -1116,8 +1116,8 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
function respondToPermission(
|
||||
permissionId: string,
|
||||
response: "once" | "always" | "reject",
|
||||
approvedPatterns: string[],
|
||||
deniedPatterns: string[],
|
||||
approvedAlways: string[],
|
||||
deniedAlways: string[],
|
||||
) {
|
||||
// Resolve sessionID from the stored permission request
|
||||
const permission = permissions().find((p) => p.id === permissionId)
|
||||
@@ -1132,8 +1132,8 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
permissionId,
|
||||
sessionID,
|
||||
response,
|
||||
approvedPatterns,
|
||||
deniedPatterns,
|
||||
approvedAlways,
|
||||
deniedAlways,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -441,10 +441,10 @@ export const dict = {
|
||||
"notification.permission.title": "مطلوب إذن",
|
||||
"notification.permission.description": "{{sessionTitle}} في {{projectName}} يحتاج إلى إذن",
|
||||
"ui.permission.permissionRules": "قواعد الأذونات",
|
||||
"ui.permission.pattern.addToAllowed": "أضف إلى قائمة المسموح",
|
||||
"ui.permission.pattern.removeFromAllowed": "أزل من قائمة المسموح",
|
||||
"ui.permission.pattern.addToDenied": "أضف إلى قائمة المرفوض",
|
||||
"ui.permission.pattern.removeFromDenied": "أزل من قائمة المرفوض",
|
||||
"ui.permission.rule.addToAllowed": "أضف إلى قائمة المسموح",
|
||||
"ui.permission.rule.removeFromAllowed": "أزل من قائمة المسموح",
|
||||
"ui.permission.rule.addToDenied": "أضف إلى قائمة المرفوض",
|
||||
"ui.permission.rule.removeFromDenied": "أزل من قائمة المرفوض",
|
||||
"notification.question.title": "سؤال",
|
||||
"notification.question.description": "{{sessionTitle}} في {{projectName}} لديه سؤال",
|
||||
"notification.action.goToSession": "انتقل إلى الجلسة",
|
||||
|
||||
@@ -443,10 +443,10 @@ export const dict = {
|
||||
"notification.permission.title": "Permissão necessária",
|
||||
"notification.permission.description": "{{sessionTitle}} em {{projectName}} precisa de permissão",
|
||||
"ui.permission.permissionRules": "Regras de permissão",
|
||||
"ui.permission.pattern.addToAllowed": "Adicionar à lista de permitidos",
|
||||
"ui.permission.pattern.removeFromAllowed": "Remover da lista de permitidos",
|
||||
"ui.permission.pattern.addToDenied": "Adicionar à lista de negados",
|
||||
"ui.permission.pattern.removeFromDenied": "Remover da lista de negados",
|
||||
"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",
|
||||
"ui.permission.rule.removeFromDenied": "Remover da lista de negados",
|
||||
"notification.question.title": "Pergunta",
|
||||
"notification.question.description": "{{sessionTitle}} em {{projectName}} tem uma pergunta",
|
||||
"notification.action.goToSession": "Ir para sessão",
|
||||
|
||||
@@ -447,10 +447,10 @@ export const dict = {
|
||||
"notification.permission.title": "Potrebna dozvola",
|
||||
"notification.permission.description": "{{sessionTitle}} u {{projectName}} traži dozvolu",
|
||||
"ui.permission.permissionRules": "Pravila dozvola",
|
||||
"ui.permission.pattern.addToAllowed": "Dodaj na listu dozvoljenih",
|
||||
"ui.permission.pattern.removeFromAllowed": "Ukloni sa liste dozvoljenih",
|
||||
"ui.permission.pattern.addToDenied": "Dodaj na listu odbijenih",
|
||||
"ui.permission.pattern.removeFromDenied": "Ukloni sa liste odbijenih",
|
||||
"ui.permission.rule.addToAllowed": "Dodaj na listu dozvoljenih",
|
||||
"ui.permission.rule.removeFromAllowed": "Ukloni sa liste dozvoljenih",
|
||||
"ui.permission.rule.addToDenied": "Dodaj na listu odbijenih",
|
||||
"ui.permission.rule.removeFromDenied": "Ukloni sa liste odbijenih",
|
||||
"notification.question.title": "Pitanje",
|
||||
"notification.question.description": "{{sessionTitle}} u {{projectName}} ima pitanje",
|
||||
"notification.action.goToSession": "Idi na sesiju",
|
||||
|
||||
@@ -444,10 +444,10 @@ export const dict = {
|
||||
"notification.permission.title": "Tilladelse påkrævet",
|
||||
"notification.permission.description": "{{sessionTitle}} i {{projectName}} kræver tilladelse",
|
||||
"ui.permission.permissionRules": "Tilladelsesregler",
|
||||
"ui.permission.pattern.addToAllowed": "Tilføj til tilladelseslisten",
|
||||
"ui.permission.pattern.removeFromAllowed": "Fjern fra tilladelseslisten",
|
||||
"ui.permission.pattern.addToDenied": "Tilføj til afvisningslisten",
|
||||
"ui.permission.pattern.removeFromDenied": "Fjern fra afvisningslisten",
|
||||
"ui.permission.rule.addToAllowed": "Tilføj til tilladelseslisten",
|
||||
"ui.permission.rule.removeFromAllowed": "Fjern fra tilladelseslisten",
|
||||
"ui.permission.rule.addToDenied": "Tilføj til afvisningslisten",
|
||||
"ui.permission.rule.removeFromDenied": "Fjern fra afvisningslisten",
|
||||
"notification.question.title": "Spørgsmål",
|
||||
"notification.question.description": "{{sessionTitle}} i {{projectName}} har et spørgsmål",
|
||||
"notification.action.goToSession": "Gå til session",
|
||||
|
||||
@@ -451,10 +451,10 @@ export const dict = {
|
||||
"notification.permission.title": "Berechtigung erforderlich",
|
||||
"notification.permission.description": "{{sessionTitle}} in {{projectName}} benötigt Berechtigung",
|
||||
"ui.permission.permissionRules": "Berechtigungsregeln",
|
||||
"ui.permission.pattern.addToAllowed": "Zur Erlaubt-Liste hinzufügen",
|
||||
"ui.permission.pattern.removeFromAllowed": "Aus der Erlaubt-Liste entfernen",
|
||||
"ui.permission.pattern.addToDenied": "Zur Verweigert-Liste hinzufügen",
|
||||
"ui.permission.pattern.removeFromDenied": "Aus der Verweigert-Liste entfernen",
|
||||
"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",
|
||||
"ui.permission.rule.removeFromDenied": "Aus der Verweigert-Liste entfernen",
|
||||
"notification.question.title": "Frage",
|
||||
"notification.question.description": "{{sessionTitle}} in {{projectName}} hat eine Frage",
|
||||
"notification.action.goToSession": "Zur Sitzung gehen",
|
||||
|
||||
@@ -446,10 +446,10 @@ export const dict = {
|
||||
"notification.permission.title": "Permission required",
|
||||
"notification.permission.description": "{{sessionTitle}} in {{projectName}} needs permission",
|
||||
"ui.permission.permissionRules": "Permission rules",
|
||||
"ui.permission.pattern.addToAllowed": "Add to allowed list",
|
||||
"ui.permission.pattern.removeFromAllowed": "Remove from allowed list",
|
||||
"ui.permission.pattern.addToDenied": "Add to denied list",
|
||||
"ui.permission.pattern.removeFromDenied": "Remove from denied list",
|
||||
"ui.permission.rule.addToAllowed": "Add to allowed list",
|
||||
"ui.permission.rule.removeFromAllowed": "Remove from allowed list",
|
||||
"ui.permission.rule.addToDenied": "Add to denied list",
|
||||
"ui.permission.rule.removeFromDenied": "Remove from denied list",
|
||||
"notification.question.title": "Question",
|
||||
"notification.question.description": "{{sessionTitle}} in {{projectName}} has a question",
|
||||
"notification.action.goToSession": "Go to session",
|
||||
|
||||
@@ -446,10 +446,10 @@ export const dict = {
|
||||
"notification.permission.title": "Permiso requerido",
|
||||
"notification.permission.description": "{{sessionTitle}} en {{projectName}} necesita permiso",
|
||||
"ui.permission.permissionRules": "Reglas de permisos",
|
||||
"ui.permission.pattern.addToAllowed": "Añadir a la lista de permitidos",
|
||||
"ui.permission.pattern.removeFromAllowed": "Quitar de la lista de permitidos",
|
||||
"ui.permission.pattern.addToDenied": "Añadir a la lista de denegados",
|
||||
"ui.permission.pattern.removeFromDenied": "Quitar de la lista de denegados",
|
||||
"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",
|
||||
"ui.permission.rule.removeFromDenied": "Quitar de la lista de denegados",
|
||||
"notification.question.title": "Pregunta",
|
||||
"notification.question.description": "{{sessionTitle}} en {{projectName}} tiene una pregunta",
|
||||
"notification.action.goToSession": "Ir a sesión",
|
||||
|
||||
@@ -451,10 +451,10 @@ export const dict = {
|
||||
"notification.permission.title": "Permission requise",
|
||||
"notification.permission.description": "{{sessionTitle}} dans {{projectName}} a besoin d'une permission",
|
||||
"ui.permission.permissionRules": "Règles de permission",
|
||||
"ui.permission.pattern.addToAllowed": "Ajouter à la liste des autorisés",
|
||||
"ui.permission.pattern.removeFromAllowed": "Retirer de la liste des autorisés",
|
||||
"ui.permission.pattern.addToDenied": "Ajouter à la liste des refusés",
|
||||
"ui.permission.pattern.removeFromDenied": "Retirer de la liste des refusés",
|
||||
"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",
|
||||
"ui.permission.rule.removeFromDenied": "Retirer de la liste des refusés",
|
||||
"notification.question.title": "Question",
|
||||
"notification.question.description": "{{sessionTitle}} dans {{projectName}} a une question",
|
||||
"notification.action.goToSession": "Aller à la session",
|
||||
|
||||
@@ -442,10 +442,10 @@ export const dict = {
|
||||
"notification.permission.title": "権限が必要です",
|
||||
"notification.permission.description": "{{projectName}} の {{sessionTitle}} が権限を必要としています",
|
||||
"ui.permission.permissionRules": "権限ルール",
|
||||
"ui.permission.pattern.addToAllowed": "許可リストに追加",
|
||||
"ui.permission.pattern.removeFromAllowed": "許可リストから削除",
|
||||
"ui.permission.pattern.addToDenied": "拒否リストに追加",
|
||||
"ui.permission.pattern.removeFromDenied": "拒否リストから削除",
|
||||
"ui.permission.rule.addToAllowed": "許可リストに追加",
|
||||
"ui.permission.rule.removeFromAllowed": "許可リストから削除",
|
||||
"ui.permission.rule.addToDenied": "拒否リストに追加",
|
||||
"ui.permission.rule.removeFromDenied": "拒否リストから削除",
|
||||
"notification.question.title": "質問",
|
||||
"notification.question.description": "{{projectName}} の {{sessionTitle}} から質問があります",
|
||||
"notification.action.goToSession": "セッションへ移動",
|
||||
|
||||
@@ -445,10 +445,10 @@ export const dict = {
|
||||
"notification.permission.title": "권한 필요",
|
||||
"notification.permission.description": "{{projectName}}의 {{sessionTitle}}에서 권한이 필요합니다",
|
||||
"ui.permission.permissionRules": "권한 규칙",
|
||||
"ui.permission.pattern.addToAllowed": "허용 목록에 추가",
|
||||
"ui.permission.pattern.removeFromAllowed": "허용 목록에서 제거",
|
||||
"ui.permission.pattern.addToDenied": "거부 목록에 추가",
|
||||
"ui.permission.pattern.removeFromDenied": "거부 목록에서 제거",
|
||||
"ui.permission.rule.addToAllowed": "허용 목록에 추가",
|
||||
"ui.permission.rule.removeFromAllowed": "허용 목록에서 제거",
|
||||
"ui.permission.rule.addToDenied": "거부 목록에 추가",
|
||||
"ui.permission.rule.removeFromDenied": "거부 목록에서 제거",
|
||||
"notification.question.title": "질문",
|
||||
"notification.question.description": "{{projectName}}의 {{sessionTitle}}에서 질문이 있습니다",
|
||||
"notification.action.goToSession": "세션으로 이동",
|
||||
|
||||
@@ -446,10 +446,10 @@ export const dict = {
|
||||
"notification.permission.title": "Tillatelse påkrevd",
|
||||
"notification.permission.description": "{{sessionTitle}} i {{projectName}} trenger tillatelse",
|
||||
"ui.permission.permissionRules": "Tillatelsesregler",
|
||||
"ui.permission.pattern.addToAllowed": "Legg til i tillatt-listen",
|
||||
"ui.permission.pattern.removeFromAllowed": "Fjern fra tillatt-listen",
|
||||
"ui.permission.pattern.addToDenied": "Legg til i blokkert-listen",
|
||||
"ui.permission.pattern.removeFromDenied": "Fjern fra blokkert-listen",
|
||||
"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",
|
||||
"ui.permission.rule.removeFromDenied": "Fjern fra blokkert-listen",
|
||||
"notification.question.title": "Spørsmål",
|
||||
"notification.question.description": "{{sessionTitle}} i {{projectName}} har et spørsmål",
|
||||
"notification.action.goToSession": "Gå til sesjon",
|
||||
|
||||
@@ -445,10 +445,10 @@ export const dict = {
|
||||
"notification.permission.title": "Wymagane uprawnienie",
|
||||
"notification.permission.description": "{{sessionTitle}} w {{projectName}} potrzebuje uprawnienia",
|
||||
"ui.permission.permissionRules": "Reguły uprawnień",
|
||||
"ui.permission.pattern.addToAllowed": "Dodaj do listy dozwolonych",
|
||||
"ui.permission.pattern.removeFromAllowed": "Usuń z listy dozwolonych",
|
||||
"ui.permission.pattern.addToDenied": "Dodaj do listy zablokowanych",
|
||||
"ui.permission.pattern.removeFromDenied": "Usuń z listy zablokowanych",
|
||||
"ui.permission.rule.addToAllowed": "Dodaj do listy dozwolonych",
|
||||
"ui.permission.rule.removeFromAllowed": "Usuń z listy dozwolonych",
|
||||
"ui.permission.rule.addToDenied": "Dodaj do listy zablokowanych",
|
||||
"ui.permission.rule.removeFromDenied": "Usuń z listy zablokowanych",
|
||||
"notification.question.title": "Pytanie",
|
||||
"notification.question.description": "{{sessionTitle}} w {{projectName}} ma pytanie",
|
||||
"notification.action.goToSession": "Przejdź do sesji",
|
||||
|
||||
@@ -447,10 +447,10 @@ export const dict = {
|
||||
"notification.permission.title": "Требуется разрешение",
|
||||
"notification.permission.description": "{{sessionTitle}} в {{projectName}} требуется разрешение",
|
||||
"ui.permission.permissionRules": "Правила разрешений",
|
||||
"ui.permission.pattern.addToAllowed": "Добавить в список разрешённых",
|
||||
"ui.permission.pattern.removeFromAllowed": "Убрать из списка разрешённых",
|
||||
"ui.permission.pattern.addToDenied": "Добавить в список запрещённых",
|
||||
"ui.permission.pattern.removeFromDenied": "Убрать из списка запрещённых",
|
||||
"ui.permission.rule.addToAllowed": "Добавить в список разрешённых",
|
||||
"ui.permission.rule.removeFromAllowed": "Убрать из списка разрешённых",
|
||||
"ui.permission.rule.addToDenied": "Добавить в список запрещённых",
|
||||
"ui.permission.rule.removeFromDenied": "Убрать из списка запрещённых",
|
||||
"notification.question.title": "Вопрос",
|
||||
"notification.question.description": "У {{sessionTitle}} в {{projectName}} есть вопрос",
|
||||
"notification.action.goToSession": "Перейти к сессии",
|
||||
|
||||
@@ -442,10 +442,10 @@ export const dict = {
|
||||
"notification.permission.title": "ต้องการสิทธิ์",
|
||||
"notification.permission.description": "{{sessionTitle}} ใน {{projectName}} ต้องการสิทธิ์",
|
||||
"ui.permission.permissionRules": "กฎการอนุญาต",
|
||||
"ui.permission.pattern.addToAllowed": "เพิ่มไปยังรายการที่อนุญาต",
|
||||
"ui.permission.pattern.removeFromAllowed": "นำออกจากรายการที่อนุญาต",
|
||||
"ui.permission.pattern.addToDenied": "เพิ่มไปยังรายการที่ถูกปฏิเสธ",
|
||||
"ui.permission.pattern.removeFromDenied": "นำออกจากรายการที่ถูกปฏิเสธ",
|
||||
"ui.permission.rule.addToAllowed": "เพิ่มไปยังรายการที่อนุญาต",
|
||||
"ui.permission.rule.removeFromAllowed": "นำออกจากรายการที่อนุญาต",
|
||||
"ui.permission.rule.addToDenied": "เพิ่มไปยังรายการที่ถูกปฏิเสธ",
|
||||
"ui.permission.rule.removeFromDenied": "นำออกจากรายการที่ถูกปฏิเสธ",
|
||||
"notification.question.title": "คำถาม",
|
||||
"notification.question.description": "{{sessionTitle}} ใน {{projectName}} มีคำถาม",
|
||||
"notification.action.goToSession": "ไปที่เซสชัน",
|
||||
|
||||
@@ -438,10 +438,10 @@ export const dict = {
|
||||
"notification.permission.title": "需要权限",
|
||||
"notification.permission.description": "{{sessionTitle}}({{projectName}})需要权限",
|
||||
"ui.permission.permissionRules": "权限规则",
|
||||
"ui.permission.pattern.addToAllowed": "添加到允许列表",
|
||||
"ui.permission.pattern.removeFromAllowed": "从允许列表移除",
|
||||
"ui.permission.pattern.addToDenied": "添加到拒绝列表",
|
||||
"ui.permission.pattern.removeFromDenied": "从拒绝列表移除",
|
||||
"ui.permission.rule.addToAllowed": "添加到允许列表",
|
||||
"ui.permission.rule.removeFromAllowed": "从允许列表移除",
|
||||
"ui.permission.rule.addToDenied": "添加到拒绝列表",
|
||||
"ui.permission.rule.removeFromDenied": "从拒绝列表移除",
|
||||
"notification.question.title": "问题",
|
||||
"notification.question.description": "{{sessionTitle}}({{projectName}})有一个问题",
|
||||
"notification.action.goToSession": "前往会话",
|
||||
|
||||
@@ -439,10 +439,10 @@ export const dict = {
|
||||
"notification.permission.title": "需要權限",
|
||||
"notification.permission.description": "{{sessionTitle}}({{projectName}})需要權限",
|
||||
"ui.permission.permissionRules": "權限規則",
|
||||
"ui.permission.pattern.addToAllowed": "加入允許清單",
|
||||
"ui.permission.pattern.removeFromAllowed": "從允許清單移除",
|
||||
"ui.permission.pattern.addToDenied": "加入拒絕清單",
|
||||
"ui.permission.pattern.removeFromDenied": "從拒絕清單移除",
|
||||
"ui.permission.rule.addToAllowed": "加入允許清單",
|
||||
"ui.permission.rule.removeFromAllowed": "從允許清單移除",
|
||||
"ui.permission.rule.addToDenied": "加入拒絕清單",
|
||||
"ui.permission.rule.removeFromDenied": "從拒絕清單移除",
|
||||
"notification.question.title": "問題",
|
||||
"notification.question.description": "{{sessionTitle}}({{projectName}})有一個問題",
|
||||
"notification.action.goToSession": "前往工作階段",
|
||||
|
||||
@@ -161,7 +161,7 @@ const globPermission: PermissionRequest = {
|
||||
sessionID: SESSION_ID,
|
||||
toolName: "glob",
|
||||
patterns: ["**/*.md"],
|
||||
args: { pattern: "**/*.md" },
|
||||
args: { pattern: "**/*.md", rules: ["**/*.md"] },
|
||||
tool: { messageID: ASST_MSG_ID, callID: "call-glob-001" },
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ const bashPermission: PermissionRequest = {
|
||||
sessionID: SESSION_ID,
|
||||
toolName: "bash",
|
||||
patterns: ["bun test"],
|
||||
args: { command: "bun test" },
|
||||
args: { command: "bun test", rules: ["bun *", "bun test"] },
|
||||
tool: { messageID: ASST_MSG_ID, callID: "call-bash-001" },
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ const dockPermission: PermissionRequest = {
|
||||
sessionID: SESSION_ID,
|
||||
toolName: "write",
|
||||
patterns: ["src/main.tsx", "src/utils.ts"],
|
||||
args: {},
|
||||
args: { rules: ["src/main.tsx", "src/utils.ts"] },
|
||||
// No `tool` field — this is a non-tool (dock) permission
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ const todoWritePermission: PermissionRequest = {
|
||||
sessionID: SESSION_ID,
|
||||
toolName: "todowrite",
|
||||
patterns: ["*"],
|
||||
args: {},
|
||||
args: { rules: ["*"] },
|
||||
tool: { messageID: ASST_MSG_ID, callID: "call-todo-001" },
|
||||
}
|
||||
|
||||
|
||||
@@ -1218,7 +1218,7 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
[data-slot="permission-patterns"] {
|
||||
[data-slot="permission-rules"] {
|
||||
margin: 0;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
@@ -1232,12 +1232,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="permission-patterns-section"] {
|
||||
[data-slot="permission-rules-section"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
[data-slot="permission-patterns-header"] {
|
||||
[data-slot="permission-rules-header"] {
|
||||
all: unset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1254,13 +1254,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="permission-patterns-header-title"] {
|
||||
[data-slot="permission-rules-header-title"] {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
[data-slot="permission-patterns-header-chevron"] {
|
||||
[data-slot="permission-rules-header-chevron"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1272,7 +1272,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="permission-patterns-collapse"] {
|
||||
[data-slot="permission-rules-collapse"] {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition:
|
||||
@@ -1286,36 +1286,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="permission-patterns-collapse-inner"] {
|
||||
[data-slot="permission-rules-collapse-inner"] {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-slot="permission-pattern-row"] {
|
||||
[data-slot="permission-rule-row"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 3px 0 3px 8px;
|
||||
min-width: 0;
|
||||
|
||||
&[data-decision="approved"] [data-slot="permission-pattern"] {
|
||||
&[data-decision="approved"] [data-slot="permission-rule"] {
|
||||
color: var(--text-success, var(--vscode-testing-iconPassed, #5cb85c));
|
||||
}
|
||||
|
||||
&[data-decision="denied"] [data-slot="permission-pattern"] {
|
||||
&[data-decision="denied"] [data-slot="permission-rule"] {
|
||||
color: var(--text-error, var(--vscode-errorForeground, #f44));
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="permission-pattern-type"] {
|
||||
[data-slot="permission-rule-type"] {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-base, var(--vscode-foreground));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-slot="permission-pattern"] {
|
||||
[data-slot="permission-rule"] {
|
||||
font-size: 12px;
|
||||
font-family: var(--vscode-editor-font-family, monospace);
|
||||
color: var(--text-weak, var(--vscode-descriptionForeground));
|
||||
@@ -1326,7 +1326,7 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
[data-slot="permission-pattern-actions"] {
|
||||
[data-slot="permission-rule-actions"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
@@ -1336,7 +1336,7 @@
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
[data-slot="permission-pattern-toggle"] {
|
||||
[data-slot="permission-rule-toggle"] {
|
||||
all: unset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -135,7 +135,7 @@ export interface PermissionRequest {
|
||||
sessionID: string
|
||||
toolName: string
|
||||
patterns: string[]
|
||||
args: Record<string, unknown>
|
||||
args: Record<string, unknown> & { rules?: string[] }
|
||||
message?: string
|
||||
tool?: { messageID: string; callID: string }
|
||||
}
|
||||
@@ -1143,8 +1143,8 @@ export interface PermissionResponseRequest {
|
||||
permissionId: string
|
||||
sessionID: string
|
||||
response: "once" | "always" | "reject"
|
||||
approvedPatterns: string[]
|
||||
deniedPatterns: string[]
|
||||
approvedAlways: string[]
|
||||
deniedAlways: string[]
|
||||
}
|
||||
|
||||
export interface CreateSessionRequest {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// kilocode_change - new file
|
||||
import { BashArity } from "@/permission/arity"
|
||||
|
||||
/**
|
||||
* Generates hierarchical always-patterns for a bash command and adds them
|
||||
* directly to the provided Set.
|
||||
*
|
||||
* Given `["npm", "install", "lodash"]` with text `"npm install lodash"`,
|
||||
* adds: `"npm *"`, `"npm install *"`, `"npm install lodash"`.
|
||||
*
|
||||
* When the exact text matches the arity prefix (e.g. `"git branch"` with
|
||||
* prefix `["git", "branch"]`), the exact text is skipped because the
|
||||
* wildcard `"git branch *"` already covers it.
|
||||
*/
|
||||
export namespace BashHierarchy {
|
||||
export function addAll(target: Set<string>, command: string[], text: string) {
|
||||
const prefix = BashArity.prefix(command)
|
||||
|
||||
// Add wildcard at each arity level: "git *", "git branch *", etc.
|
||||
for (let i = 1; i <= prefix.length; i++) {
|
||||
target.add(prefix.slice(0, i).join(" ") + " *")
|
||||
}
|
||||
|
||||
// Add exact text only when it adds specificity beyond the arity prefix.
|
||||
// e.g. if text is "git log --oneline", add it; if it's only "git log",
|
||||
// no need to add it because arity already generates "git log *" as a prefix.
|
||||
if (text !== prefix.join(" ")) target.add(text)
|
||||
}
|
||||
}
|
||||
@@ -161,25 +161,25 @@ export namespace PermissionNext {
|
||||
)
|
||||
|
||||
// kilocode_change start
|
||||
export const savePatternRules = fn(
|
||||
export const saveAlwaysRules = fn(
|
||||
z.object({
|
||||
requestID: Identifier.schema("permission"),
|
||||
approvedPatterns: z.string().array().optional(),
|
||||
deniedPatterns: z.string().array().optional(),
|
||||
approvedAlways: z.string().array().optional(),
|
||||
deniedAlways: z.string().array().optional(),
|
||||
}),
|
||||
async (input) => {
|
||||
const s = await state()
|
||||
const existing = s.pending[input.requestID]
|
||||
if (!existing) throw new NotFoundError({ message: `Permission request ${input.requestID} not found` })
|
||||
|
||||
const validPatterns = new Set(existing.info.patterns)
|
||||
const validRules = new Set(existing.info.metadata?.rules ?? [])
|
||||
const permission = existing.info.permission
|
||||
|
||||
for (const pattern of input.approvedPatterns ?? []) {
|
||||
if (validPatterns.has(pattern)) s.approved.push({ permission, pattern, action: "allow" })
|
||||
for (const pattern of input.approvedAlways ?? []) {
|
||||
if (validRules.has(pattern)) s.approved.push({ permission, pattern, action: "allow" })
|
||||
}
|
||||
for (const pattern of input.deniedPatterns ?? []) {
|
||||
if (validPatterns.has(pattern)) s.approved.push({ permission, pattern, action: "deny" })
|
||||
for (const pattern of input.deniedAlways ?? []) {
|
||||
if (validRules.has(pattern)) s.approved.push({ permission, pattern, action: "deny" })
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -51,14 +51,14 @@ export const PermissionRoutes = lazy(() =>
|
||||
)
|
||||
// kilocode_change start
|
||||
.post(
|
||||
"/:requestID/pattern-rules",
|
||||
"/:requestID/always-rules",
|
||||
describeRoute({
|
||||
summary: "Save per-pattern permission rules",
|
||||
description: "Save approved/denied patterns for a pending permission request.",
|
||||
operationId: "permission.savePatternRules",
|
||||
summary: "Save always-allow/deny permission rules",
|
||||
description: "Save approved/denied always-rules for a pending permission request.",
|
||||
operationId: "permission.saveAlwaysRules",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Pattern rules saved successfully",
|
||||
description: "Always rules saved successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
@@ -77,17 +77,17 @@ export const PermissionRoutes = lazy(() =>
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
approvedPatterns: z.string().array().optional(),
|
||||
deniedPatterns: z.string().array().optional(),
|
||||
approvedAlways: z.string().array().optional(),
|
||||
deniedAlways: z.string().array().optional(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const json = c.req.valid("json")
|
||||
await PermissionNext.savePatternRules({
|
||||
await PermissionNext.saveAlwaysRules({
|
||||
requestID: params.requestID,
|
||||
approvedPatterns: json.approvedPatterns,
|
||||
deniedPatterns: json.deniedPatterns,
|
||||
approvedAlways: json.approvedAlways,
|
||||
deniedAlways: json.deniedAlways,
|
||||
})
|
||||
return c.json(true)
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Flag } from "@/flag/flag.ts"
|
||||
import { Shell } from "@/shell/shell"
|
||||
|
||||
import { BashArity } from "@/permission/arity"
|
||||
import { BashHierarchy } from "@/kilocode/bash-hierarchy" // kilocode_change
|
||||
import { Truncate } from "./truncation"
|
||||
import { Plugin } from "@/plugin"
|
||||
|
||||
@@ -90,6 +91,7 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
if (!Instance.containsPath(cwd)) directories.add(cwd)
|
||||
const patterns = new Set<string>()
|
||||
const always = new Set<string>()
|
||||
const rules = new Set<string>() // kilocode_change — hierarchy rules for permissions "npm", "npm install", "npm install lodash"
|
||||
|
||||
for (const node of tree.rootNode.descendantsOfType("command")) {
|
||||
if (!node) continue
|
||||
@@ -139,6 +141,7 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
if (command.length && command[0] !== "cd") {
|
||||
patterns.add(commandText)
|
||||
always.add(BashArity.prefix(command).join(" ") + " *")
|
||||
BashHierarchy.addAll(rules, command, commandText) // kilocode_change
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +164,7 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
permission: "bash",
|
||||
patterns: Array.from(patterns),
|
||||
always: Array.from(always),
|
||||
metadata: { command: params.command }, // kilocode_change
|
||||
metadata: { command: params.command, rules: Array.from(rules) }, // kilocode_change
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { test, expect, describe } from "bun:test"
|
||||
import { BashHierarchy } from "../../src/kilocode/bash-hierarchy"
|
||||
|
||||
function collect(command: string[], text: string): string[] {
|
||||
const set = new Set<string>()
|
||||
BashHierarchy.addAll(set, command, text)
|
||||
return [...set]
|
||||
}
|
||||
|
||||
describe("BashHierarchy.addAll", () => {
|
||||
test("arity-1 command with args produces base wildcard + exact", () => {
|
||||
// "ls" has arity 1, prefix = ["ls"], text "ls -la" !== "ls" → exact is added
|
||||
const result = collect(["ls", "-la"], "ls -la")
|
||||
expect(result).toContain("ls *")
|
||||
expect(result).toContain("ls -la")
|
||||
})
|
||||
|
||||
test("arity-2 command without extra args skips redundant exact text", () => {
|
||||
// "git status" has arity 2, prefix = ["git", "status"], text === prefix → no exact
|
||||
const result = collect(["git", "status"], "git status")
|
||||
expect(result).toEqual(["git *", "git status *"])
|
||||
})
|
||||
|
||||
test("arity-2 command with extra args includes exact text", () => {
|
||||
// "npm install lodash" has arity 2, prefix = ["npm", "install"], text !== prefix → exact added
|
||||
const result = collect(["npm", "install", "lodash"], "npm install lodash")
|
||||
expect(result).toEqual(["npm *", "npm install *", "npm install lodash"])
|
||||
})
|
||||
|
||||
test("arity-3 command without extra args skips redundant exact text", () => {
|
||||
// "npm run dev" has arity 3, prefix = ["npm", "run", "dev"], text === prefix → no exact
|
||||
const result = collect(["npm", "run", "dev"], "npm run dev")
|
||||
expect(result).toEqual(["npm *", "npm run *", "npm run dev *"])
|
||||
})
|
||||
|
||||
test("arity-3 command with extra args includes exact text", () => {
|
||||
const result = collect(["docker", "compose", "up", "-d"], "docker compose up -d")
|
||||
expect(result).toEqual(["docker *", "docker compose *", "docker compose up *", "docker compose up -d"])
|
||||
})
|
||||
|
||||
test("single token command without args skips redundant exact text", () => {
|
||||
// "pwd" has arity 1, prefix = ["pwd"], text === prefix → no exact
|
||||
const result = collect(["pwd"], "pwd")
|
||||
expect(result).toEqual(["pwd *"])
|
||||
})
|
||||
|
||||
test("empty command returns empty", () => {
|
||||
const result = collect([], "")
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
test("unknown command with args includes exact text", () => {
|
||||
const result = collect(["mycustomtool", "arg1", "arg2"], "mycustomtool arg1 arg2")
|
||||
expect(result).toEqual(["mycustomtool *", "mycustomtool arg1 arg2"])
|
||||
})
|
||||
|
||||
test("duplicates are deduplicated by Set", () => {
|
||||
const set = new Set<string>()
|
||||
BashHierarchy.addAll(set, ["git", "status"], "git status")
|
||||
BashHierarchy.addAll(set, ["git", "diff"], "git diff")
|
||||
// "git *" appears in both but Set deduplicates
|
||||
expect([...set].filter((p) => p === "git *")).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,169 @@
|
||||
import { test, expect, describe } from "bun:test"
|
||||
import { PermissionNext } from "../../src/permission/next"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { NotFoundError } from "../../src/storage/db"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
describe("saveAlwaysRules", () => {
|
||||
test("approved rules auto-allow future requests", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_1",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: { rules: ["npm *", "npm install"] },
|
||||
always: ["npm install *"],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
await PermissionNext.saveAlwaysRules({ requestID: "permission_1", approvedAlways: ["npm install"] })
|
||||
await PermissionNext.reply({ requestID: "permission_1", reply: "once" })
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
const result = await PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
expect(result).toBeUndefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("denied rules auto-deny future requests", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_2",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["rm -rf /"],
|
||||
metadata: { rules: ["rm *", "rm -rf /"] },
|
||||
always: ["rm *"],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
await PermissionNext.saveAlwaysRules({ requestID: "permission_2", deniedAlways: ["rm -rf /"] })
|
||||
await PermissionNext.reply({ requestID: "permission_2", reply: "once" })
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
await expect(
|
||||
PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["rm -rf /"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(PermissionNext.DeniedError)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("throws for unknown request ID", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await expect(
|
||||
PermissionNext.saveAlwaysRules({ requestID: "permission_nonexistent", approvedAlways: ["npm install"] }),
|
||||
).rejects.toBeInstanceOf(NotFoundError)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("ignores patterns not in metadata.rules", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_3",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: { rules: ["npm *", "npm install"] },
|
||||
always: ["npm install *"],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
// "curl" is not in metadata.rules — should be silently ignored
|
||||
await PermissionNext.saveAlwaysRules({
|
||||
requestID: "permission_3",
|
||||
approvedAlways: ["npm install", "curl http://evil.com"],
|
||||
})
|
||||
|
||||
await PermissionNext.reply({ requestID: "permission_3", reply: "once" })
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
// npm install was in rules — auto-allowed
|
||||
const result = await PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
expect(result).toBeUndefined()
|
||||
|
||||
// curl was NOT in rules — still requires permission
|
||||
const curlPromise = PermissionNext.ask({
|
||||
id: "permission_curl",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["curl http://evil.com"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
await PermissionNext.reply({ requestID: "permission_curl", reply: "reject" })
|
||||
await expect(curlPromise).rejects.toBeInstanceOf(PermissionNext.RejectedError)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("accepts hierarchy patterns from metadata.rules", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_4",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install lodash"],
|
||||
metadata: { rules: ["npm *", "npm install *", "npm install lodash"] },
|
||||
always: ["npm install *"],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
// Approve the broadest hierarchy level
|
||||
await PermissionNext.saveAlwaysRules({ requestID: "permission_4", approvedAlways: ["npm *"] })
|
||||
await PermissionNext.reply({ requestID: "permission_4", reply: "once" })
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
// "npm *" wildcard should auto-allow any npm command
|
||||
const result = await PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm test"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
expect(result).toBeUndefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,317 +0,0 @@
|
||||
import { test, expect } from "bun:test"
|
||||
import { PermissionNext } from "../../src/permission/next"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { NotFoundError } from "../../src/storage/db"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
test("savePatternRules - approvedPatterns saves allow rules for future requests", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_approved1",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
// Save pattern rules before replying
|
||||
await PermissionNext.savePatternRules({
|
||||
requestID: "permission_approved1",
|
||||
approvedPatterns: ["npm install"],
|
||||
})
|
||||
|
||||
await PermissionNext.reply({
|
||||
requestID: "permission_approved1",
|
||||
reply: "once",
|
||||
})
|
||||
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
// The approved pattern should now auto-allow future requests
|
||||
const result = await PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
expect(result).toBeUndefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("savePatternRules - deniedPatterns saves deny rules for future requests", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_denied1",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["rm -rf /"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
// Save pattern rules before replying
|
||||
await PermissionNext.savePatternRules({
|
||||
requestID: "permission_denied1",
|
||||
deniedPatterns: ["rm -rf /"],
|
||||
})
|
||||
|
||||
await PermissionNext.reply({
|
||||
requestID: "permission_denied1",
|
||||
reply: "once",
|
||||
})
|
||||
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
// The denied pattern should now auto-deny future requests
|
||||
await expect(
|
||||
PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["rm -rf /"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(PermissionNext.DeniedError)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("savePatternRules - multiple bash commands: approve some, deny others", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_multi1",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install", "npm test", "rm -rf /tmp/cache"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
// Approve npm commands, deny the rm command
|
||||
await PermissionNext.savePatternRules({
|
||||
requestID: "permission_multi1",
|
||||
approvedPatterns: ["npm install", "npm test"],
|
||||
deniedPatterns: ["rm -rf /tmp/cache"],
|
||||
})
|
||||
|
||||
await PermissionNext.reply({
|
||||
requestID: "permission_multi1",
|
||||
reply: "once",
|
||||
})
|
||||
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
// Approved patterns should auto-allow
|
||||
const result1 = await PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
expect(result1).toBeUndefined()
|
||||
|
||||
const result2 = await PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm test"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
expect(result2).toBeUndefined()
|
||||
|
||||
// Denied pattern should auto-deny
|
||||
await expect(
|
||||
PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["rm -rf /tmp/cache"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(PermissionNext.DeniedError)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("savePatternRules - multiple bash commands: approve all patterns", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_multi2",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["git status", "git diff", "git log --oneline"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
await PermissionNext.savePatternRules({
|
||||
requestID: "permission_multi2",
|
||||
approvedPatterns: ["git status", "git diff", "git log --oneline"],
|
||||
})
|
||||
|
||||
await PermissionNext.reply({
|
||||
requestID: "permission_multi2",
|
||||
reply: "once",
|
||||
})
|
||||
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
// All three should auto-allow in a single request with multiple patterns
|
||||
const result = await PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["git status", "git diff", "git log --oneline"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
expect(result).toBeUndefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("savePatternRules - ignores patterns not in the original request", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_multi3",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
// Try to sneak in an unrelated pattern — should be ignored
|
||||
await PermissionNext.savePatternRules({
|
||||
requestID: "permission_multi3",
|
||||
approvedPatterns: ["npm install", "curl http://evil.com"],
|
||||
})
|
||||
|
||||
await PermissionNext.reply({
|
||||
requestID: "permission_multi3",
|
||||
reply: "once",
|
||||
})
|
||||
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
// npm install was in the original request — should be auto-allowed
|
||||
const result = await PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["npm install"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
expect(result).toBeUndefined()
|
||||
|
||||
// curl was NOT in the original request — should still require permission (ask)
|
||||
const curlPromise = PermissionNext.ask({
|
||||
id: "permission_curl_check",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["curl http://evil.com"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
// Should be pending (not auto-resolved), meaning it returned a Promise
|
||||
expect(curlPromise).toBeInstanceOf(Promise)
|
||||
|
||||
// Clean up the pending request — reject throws RejectedError on the promise
|
||||
await PermissionNext.reply({
|
||||
requestID: "permission_curl_check",
|
||||
reply: "reject",
|
||||
})
|
||||
await expect(curlPromise).rejects.toBeInstanceOf(PermissionNext.RejectedError)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("savePatternRules - throws error for stale/unknown request ID", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await expect(
|
||||
PermissionNext.savePatternRules({
|
||||
requestID: "permission_nonexistent",
|
||||
approvedPatterns: ["npm install"],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(NotFoundError)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("savePatternRules - multiple bash commands: deny all patterns", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const askPromise = PermissionNext.ask({
|
||||
id: "permission_multi4",
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["rm -rf /", "sudo shutdown", "dd if=/dev/zero of=/dev/sda"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
})
|
||||
|
||||
await PermissionNext.savePatternRules({
|
||||
requestID: "permission_multi4",
|
||||
deniedPatterns: ["rm -rf /", "sudo shutdown", "dd if=/dev/zero of=/dev/sda"],
|
||||
})
|
||||
|
||||
await PermissionNext.reply({
|
||||
requestID: "permission_multi4",
|
||||
reply: "once",
|
||||
})
|
||||
|
||||
await expect(askPromise).resolves.toBeUndefined()
|
||||
|
||||
// Each denied pattern should auto-deny individually
|
||||
for (const pattern of ["rm -rf /", "sudo shutdown", "dd if=/dev/zero of=/dev/sda"]) {
|
||||
await expect(
|
||||
PermissionNext.ask({
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: [pattern],
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(PermissionNext.DeniedError)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -306,8 +306,10 @@ describe("tool.bash permissions", () => {
|
||||
await bash.execute({ command: "ls -la", description: "List" }, testCtx)
|
||||
const bashReq = requests.find((r) => r.permission === "bash")
|
||||
expect(bashReq).toBeDefined()
|
||||
const pattern = bashReq!.always[0]
|
||||
expect(pattern).toBe("ls *")
|
||||
// kilocode_change start — hierarchy adds base wildcard + exact
|
||||
expect(bashReq!.always).toContain("ls *")
|
||||
expect(bashReq!.metadata.rules).toContain("ls -la")
|
||||
// kilocode_change end
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -98,8 +98,8 @@ import type {
|
||||
PermissionRespondErrors,
|
||||
PermissionRespondResponses,
|
||||
PermissionRuleset,
|
||||
PermissionSavePatternRulesErrors,
|
||||
PermissionSavePatternRulesResponses,
|
||||
PermissionSaveAlwaysRulesErrors,
|
||||
PermissionSaveAlwaysRulesResponses,
|
||||
ProjectCurrentResponses,
|
||||
ProjectListResponses,
|
||||
ProjectUpdateErrors,
|
||||
@@ -2464,17 +2464,17 @@ export class Permission extends HeyApiClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save per-pattern permission rules
|
||||
* Save always-allow/deny permission rules
|
||||
*
|
||||
* Save approved/denied patterns for a pending permission request.
|
||||
* Save approved/denied always-rules for a pending permission request.
|
||||
*/
|
||||
public savePatternRules<ThrowOnError extends boolean = false>(
|
||||
public saveAlwaysRules<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
requestID: string
|
||||
directory?: string
|
||||
workspace?: string
|
||||
approvedPatterns?: Array<string>
|
||||
deniedPatterns?: Array<string>
|
||||
approvedAlways?: Array<string>
|
||||
deniedAlways?: Array<string>
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -2486,18 +2486,18 @@ export class Permission extends HeyApiClient {
|
||||
{ in: "path", key: "requestID" },
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "body", key: "approvedPatterns" },
|
||||
{ in: "body", key: "deniedPatterns" },
|
||||
{ in: "body", key: "approvedAlways" },
|
||||
{ in: "body", key: "deniedAlways" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<
|
||||
PermissionSavePatternRulesResponses,
|
||||
PermissionSavePatternRulesErrors,
|
||||
PermissionSaveAlwaysRulesResponses,
|
||||
PermissionSaveAlwaysRulesErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/permission/{requestID}/pattern-rules",
|
||||
url: "/permission/{requestID}/always-rules",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
|
||||
@@ -3947,10 +3947,10 @@ export type PermissionReplyResponses = {
|
||||
|
||||
export type PermissionReplyResponse = PermissionReplyResponses[keyof PermissionReplyResponses]
|
||||
|
||||
export type PermissionSavePatternRulesData = {
|
||||
export type PermissionSaveAlwaysRulesData = {
|
||||
body?: {
|
||||
approvedPatterns?: Array<string>
|
||||
deniedPatterns?: Array<string>
|
||||
approvedAlways?: Array<string>
|
||||
deniedAlways?: Array<string>
|
||||
}
|
||||
path: {
|
||||
requestID: string
|
||||
@@ -3959,10 +3959,10 @@ export type PermissionSavePatternRulesData = {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/permission/{requestID}/pattern-rules"
|
||||
url: "/permission/{requestID}/always-rules"
|
||||
}
|
||||
|
||||
export type PermissionSavePatternRulesErrors = {
|
||||
export type PermissionSaveAlwaysRulesErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
@@ -3973,17 +3973,17 @@ export type PermissionSavePatternRulesErrors = {
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type PermissionSavePatternRulesError = PermissionSavePatternRulesErrors[keyof PermissionSavePatternRulesErrors]
|
||||
export type PermissionSaveAlwaysRulesError = PermissionSaveAlwaysRulesErrors[keyof PermissionSaveAlwaysRulesErrors]
|
||||
|
||||
export type PermissionSavePatternRulesResponses = {
|
||||
export type PermissionSaveAlwaysRulesResponses = {
|
||||
/**
|
||||
* Pattern rules saved successfully
|
||||
* Always rules saved successfully
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type PermissionSavePatternRulesResponse =
|
||||
PermissionSavePatternRulesResponses[keyof PermissionSavePatternRulesResponses]
|
||||
export type PermissionSaveAlwaysRulesResponse =
|
||||
PermissionSaveAlwaysRulesResponses[keyof PermissionSaveAlwaysRulesResponses]
|
||||
|
||||
export type PermissionListData = {
|
||||
body?: never
|
||||
|
||||
@@ -4320,9 +4320,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/permission/{requestID}/pattern-rules": {
|
||||
"/permission/{requestID}/always-rules": {
|
||||
"post": {
|
||||
"operationId": "permission.savePatternRules",
|
||||
"operationId": "permission.saveAlwaysRules",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
@@ -4347,11 +4347,11 @@
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"summary": "Save per-pattern permission rules",
|
||||
"description": "Save approved/denied patterns for a pending permission request.",
|
||||
"summary": "Save always-allow/deny permission rules",
|
||||
"description": "Save approved/denied always-rules for a pending permission request.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Pattern rules saved successfully",
|
||||
"description": "Always rules saved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
@@ -4387,13 +4387,13 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"approvedPatterns": {
|
||||
"approvedAlways": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"deniedPatterns": {
|
||||
"deniedAlways": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -4407,7 +4407,7 @@
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.permission.savePatternRules({\n ...\n})"
|
||||
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.permission.saveAlwaysRules({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user