Remove allow always button (#7159)

* feat: save permissions to config

* fix: preserve wildcard default in jsonc scalar promotion and skip dispose for permission saves

* fix: update CLI permission hint to reflect permanent persistence

* fix: avoid mutating input in mergeConfig permission normalization

* chore: update kilo-vscode visual regression baselines

* fix(vscode): remove Allow Always button from PermissionDock

* fix(kilo-vscode): removed unreachable onDecide option

* chore: update kilo-vscode visual regression baselines

* fix: scope patchJsonc scalar promotion to permission keys only

* fix: toConfig always uses object format to preserve existing granular rules

* fix: preserve metadata.rules order in saveAlwaysRules for correct precedence

* feat:show non-bash tool permissions on dropdown

* fix: include always patterns in saveAlwaysRules validation loop

* test(kilo-vscode): remove write mixed-decisions screenshot test for single-rule dropdown

* test: updated visual tests

* fix: formatting

* chore: update kilo-vscode visual regression baselines

* fix: use scalar format in toConfig for PermissionAction-only permissions

* fix: serialize scalar-only permissions as scalars for all patterns

* fix: skip non-wildcard patterns for scalar-only permissions in toConfig

* fix: promote doom_loop to PermissionRule for per-tool persistence

* fix: keep doom_loop as PermissionAction and skip non-wildcard scalar-only patterns in toConfig

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Imanol Maiztegui
2026-03-17 18:44:04 +01:00
committed by GitHub
co-authored by github-actions[bot]
parent ac0a3ae997
commit 0adb514da4
47 changed files with 191 additions and 119 deletions
@@ -82,7 +82,7 @@ describe("saveAlwaysRules", () => {
})
})
test("ignores patterns not in metadata.rules", async () => {
test("ignores patterns not in metadata.rules or always", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
@@ -97,7 +97,7 @@ describe("saveAlwaysRules", () => {
ruleset: [],
})
// "curl" is not in metadata.rules — should be silently ignored
// "curl" is not in metadata.rules or always — should be silently ignored
await PermissionNext.saveAlwaysRules({
requestID: "permission_3",
approvedAlways: ["npm install", "curl http://evil.com"],
@@ -133,6 +133,40 @@ describe("saveAlwaysRules", () => {
})
})
test("accepts patterns from always array (non-bash tools)", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const askPromise = PermissionNext.ask({
id: "permission_nonbash",
sessionID: "session_test",
permission: "read",
patterns: ["src/main.ts"],
metadata: {},
always: ["*"],
ruleset: [],
})
// "*" is in always — should be accepted even without metadata.rules
await PermissionNext.saveAlwaysRules({ requestID: "permission_nonbash", approvedAlways: ["*"] })
await PermissionNext.reply({ requestID: "permission_nonbash", reply: "once" })
await expect(askPromise).resolves.toBeUndefined()
// "*" wildcard should auto-allow any read
const result = await PermissionNext.ask({
sessionID: "session_test",
permission: "read",
patterns: ["any/file.ts"],
metadata: {},
always: [],
ruleset: [],
})
expect(result).toBeUndefined()
},
})
})
test("accepts hierarchy patterns from metadata.rules", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
@@ -60,3 +60,29 @@ test("toConfig - roundtrip with fromConfig (object)", () => {
const result = PermissionNext.toConfig(rules)
expect(result).toEqual(config)
})
test("toConfig - scalar-only permission uses scalar format", () => {
const result = PermissionNext.toConfig([{ permission: "websearch", pattern: "*", action: "allow" }])
expect(result).toEqual({ websearch: "allow" })
})
test("toConfig - scalar-only permission with non-wildcard pattern is skipped", () => {
// doom_loop uses always: [toolName], so pattern can be "bash" etc.
// Non-wildcard patterns for scalar-only permissions can't be represented
// in the config schema — they only work in-memory (known limitation).
const result = PermissionNext.toConfig([{ permission: "doom_loop", pattern: "bash", action: "allow" }])
expect(result).toEqual({})
})
test("toConfig - mixed scalar-only and rule-capable permissions", () => {
const result = PermissionNext.toConfig([
{ permission: "websearch", pattern: "*", action: "allow" },
{ permission: "todowrite", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "npm *", action: "allow" },
])
expect(result).toEqual({
websearch: "allow",
todowrite: "allow",
bash: { "npm *": "allow" },
})
})