-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(vscode): add calculated permissions display and subagent visibility to agent details #8505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bd0295b
97ce209
cc257fd
359410f
69754b8
cb7058c
7dc4399
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import { Component, Show, createMemo } from "solid-js" | ||
| import { Component, Show, For, createMemo, createSignal } from "solid-js" | ||
| import { TextField } from "@kilocode/kilo-ui/text-field" | ||
| import { Switch } from "@kilocode/kilo-ui/switch" | ||
| import { Card } from "@kilocode/kilo-ui/card" | ||
|
|
@@ -8,7 +8,7 @@ import { IconButton } from "@kilocode/kilo-ui/icon-button" | |
| import { useConfig } from "../../context/config" | ||
| import { useSession } from "../../context/session" | ||
| import { useLanguage } from "../../context/language" | ||
| import type { AgentConfig, AgentInfo } from "../../types/messages" | ||
| import type { AgentConfig, AgentInfo, PermissionRuleItem } from "../../types/messages" | ||
| import SettingsRow from "./SettingsRow" | ||
| import { buildExport } from "./mode-io" | ||
|
|
||
|
|
@@ -26,8 +26,9 @@ const ModeEditView: Component<Props> = (props) => { | |
| // agent() may be undefined for modes that only exist in the config draft (just | ||
| // created, not yet saved). This is fine — native defaults to false (correct for | ||
| // custom modes) and all fields read from cfg() which comes from config context. | ||
| const agent = () => session.agents().find((a) => a.name === props.name) | ||
| const agent = () => session.allAgents().find((a) => a.name === props.name) | ||
| const native = () => agent()?.native ?? false | ||
| const [expanded, setExpanded] = createSignal(false) | ||
|
|
||
| const cfg = createMemo<AgentConfig>(() => config().agent?.[props.name] ?? {}) | ||
|
|
||
|
|
@@ -230,6 +231,18 @@ const ModeEditView: Component<Props> = (props) => { | |
| </SettingsRow> | ||
| </Card> | ||
|
|
||
| {/* Calculated permissions (read-only, collapsible) */} | ||
| <Show when={agent()?.permission} keyed> | ||
| {(rules) => ( | ||
| <PermissionRuleset | ||
| agent={props.name} | ||
| rules={rules} | ||
| expanded={expanded()} | ||
| onToggle={() => setExpanded((v) => !v)} | ||
| /> | ||
| )} | ||
| </Show> | ||
|
|
||
| <div style={{ display: "flex", "justify-content": "flex-end" }}> | ||
| <Button variant="ghost" onClick={props.onBack}> | ||
| {language.t("settings.agentBehaviour.editMode.back")} | ||
|
|
@@ -239,4 +252,201 @@ const ModeEditView: Component<Props> = (props) => { | |
| ) | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Collapsible permissions ruleset display | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| const ACTION_COLORS: Record<string, { bg: string; fg: string }> = { | ||
| allow: { bg: "var(--vscode-terminal-ansiGreen, #3fb950)", fg: "var(--vscode-editor-background, #1e1e1e)" }, | ||
| ask: { bg: "var(--vscode-editorWarning-foreground, #cca700)", fg: "var(--vscode-editor-background, #1e1e1e)" }, | ||
| deny: { bg: "var(--vscode-errorForeground, #f85149)", fg: "var(--vscode-editor-background, #fff)" }, | ||
| unknown: { bg: "var(--vscode-descriptionForeground, #8b949e)", fg: "var(--vscode-editor-background, #1e1e1e)" }, | ||
| } | ||
|
|
||
| interface RulesetProps { | ||
| agent: string | ||
| rules: PermissionRuleItem[] | ||
| expanded: boolean | ||
| onToggle: () => void | ||
| } | ||
|
|
||
| const PermissionRuleset: Component<RulesetProps> = (props) => { | ||
| const language = useLanguage() | ||
| const [copied, setCopied] = createSignal(false) | ||
|
|
||
| // Compute effective action per unique tool by finding the last rule with pattern "*" | ||
| // NOTE: This assumes the CLI uses "*" as the wildcard pattern for catch-all rules. | ||
| // If the CLI convention changes (e.g. to "**" or another pattern), this will need updating. | ||
| const summary = createMemo(() => { | ||
| const tools = new Map<string, PermissionRuleItem["action"]>() | ||
| for (const rule of props.rules) { | ||
| if (rule.pattern === "*") { | ||
| tools.set(rule.permission, rule.action) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Wildcard permission rules render a misleading summary The CLI already emits catch-all rules like |
||
| } | ||
| } | ||
| return [...tools.entries()].sort((a, b) => a[0].localeCompare(b[0])) | ||
| }) | ||
|
|
||
| const copy = (e: MouseEvent) => { | ||
| e.stopPropagation() | ||
| const data = { agent: props.agent, rules: props.rules } | ||
| navigator.clipboard.writeText(JSON.stringify(data, null, 2)) | ||
| setCopied(true) | ||
| setTimeout(() => setCopied(false), 2000) | ||
| } | ||
|
|
||
| return ( | ||
| <Card style={{ "margin-bottom": "12px" }}> | ||
| <div | ||
| style={{ display: "flex", "align-items": "center", cursor: "pointer", "user-select": "none" }} | ||
| onClick={props.onToggle} | ||
| > | ||
| <IconButton | ||
| size="small" | ||
| variant="ghost" | ||
| icon={props.expanded ? "chevron-down" : "chevron-right"} | ||
| onClick={(e: MouseEvent) => { | ||
| e.stopPropagation() | ||
| props.onToggle() | ||
| }} | ||
| /> | ||
| <span data-slot="settings-row-label-title" style={{ "margin-left": "4px" }}> | ||
| {language.t("settings.agentBehaviour.permissions.title")} | ||
| </span> | ||
| <span | ||
| style={{ | ||
| "margin-left": "8px", | ||
| "font-size": "11px", | ||
| color: "var(--text-weak-base, var(--vscode-descriptionForeground))", | ||
| }} | ||
| > | ||
| {language.t("settings.agentBehaviour.permissions.count", { count: String(props.rules.length) })} | ||
| </span> | ||
| <div style={{ "margin-left": "auto" }}> | ||
| <IconButton | ||
| size="small" | ||
| variant="ghost" | ||
| icon={copied() ? "check" : "copy"} | ||
| title={language.t("settings.agentBehaviour.permissions.copy")} | ||
| onClick={copy} | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Show when={props.expanded}> | ||
| {/* Summary: effective action per tool for wildcard pattern */} | ||
| <Show when={summary().length > 0}> | ||
| <div style={{ "margin-top": "8px", "margin-bottom": "8px" }}> | ||
| <div | ||
| style={{ | ||
| "font-size": "11px", | ||
| color: "var(--text-weak-base, var(--vscode-descriptionForeground))", | ||
| "margin-bottom": "4px", | ||
| }} | ||
| > | ||
| {language.t("settings.agentBehaviour.permissions.effective")} | ||
| </div> | ||
| <div style={{ display: "flex", "flex-wrap": "wrap", gap: "4px" }}> | ||
| <For each={summary()}> | ||
| {([tool, action]) => { | ||
| const colors = ACTION_COLORS[action] ?? ACTION_COLORS.unknown | ||
| return ( | ||
| <span | ||
| style={{ | ||
| "font-size": "11px", | ||
| padding: "2px 6px", | ||
| "border-radius": "3px", | ||
| background: colors.bg, | ||
| color: colors.fg, | ||
| "font-family": "var(--vscode-editor-font-family, monospace)", | ||
| }} | ||
| > | ||
| {tool}: {action} | ||
| </span> | ||
| ) | ||
| }} | ||
| </For> | ||
| </div> | ||
| </div> | ||
| </Show> | ||
|
|
||
| {/* Full ruleset table */} | ||
| <div | ||
| style={{ | ||
| "margin-top": "8px", | ||
| "font-size": "11px", | ||
| "font-family": "var(--vscode-editor-font-family, monospace)", | ||
| "max-height": "300px", | ||
| "overflow-y": "auto", | ||
| border: "1px solid var(--border-weak-base, var(--vscode-panel-border))", | ||
| "border-radius": "4px", | ||
| }} | ||
| > | ||
| <table style={{ width: "100%", "border-collapse": "collapse" }}> | ||
| <thead> | ||
| <tr | ||
| style={{ | ||
| background: "var(--bg-subtle-base, var(--vscode-editorWidget-background))", | ||
| position: "sticky", | ||
| top: "0", | ||
| }} | ||
| > | ||
| <th style={{ padding: "4px 8px", "text-align": "left", "font-weight": "600" }}> | ||
| {language.t("settings.agentBehaviour.permissions.col.tool")} | ||
| </th> | ||
| <th style={{ padding: "4px 8px", "text-align": "left", "font-weight": "600" }}> | ||
| {language.t("settings.agentBehaviour.permissions.col.pattern")} | ||
| </th> | ||
| <th style={{ padding: "4px 8px", "text-align": "left", "font-weight": "600" }}> | ||
| {language.t("settings.agentBehaviour.permissions.col.action")} | ||
| </th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <For each={props.rules}> | ||
| {(rule, idx) => { | ||
| const colors = ACTION_COLORS[rule.action] ?? ACTION_COLORS.unknown | ||
| return ( | ||
| <tr | ||
| style={{ | ||
| "border-top": | ||
| idx() > 0 ? "1px solid var(--border-weak-base, var(--vscode-panel-border))" : "none", | ||
| }} | ||
| > | ||
| <td style={{ padding: "3px 8px" }}>{rule.permission}</td> | ||
| <td style={{ padding: "3px 8px", color: "var(--text-weak-base)" }}>{rule.pattern}</td> | ||
| <td style={{ padding: "3px 8px" }}> | ||
| <span | ||
| style={{ | ||
| padding: "1px 4px", | ||
| "border-radius": "2px", | ||
| background: colors.bg, | ||
| color: colors.fg, | ||
| }} | ||
| > | ||
| {rule.action} | ||
| </span> | ||
| </td> | ||
| </tr> | ||
| ) | ||
| }} | ||
| </For> | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
|
|
||
| <div | ||
| style={{ | ||
| "margin-top": "6px", | ||
| "font-size": "10px", | ||
| color: "var(--text-weak-base, var(--vscode-descriptionForeground))", | ||
| }} | ||
| > | ||
| {language.t("settings.agentBehaviour.permissions.hint")} | ||
| </div> | ||
| </Show> | ||
| </Card> | ||
| ) | ||
| } | ||
|
|
||
| export default ModeEditView | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably refactor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, @imanolmzd-svg indicated he was looking into it, but I don't think this specific pr is a big offender