Save permission rules to config (#7143)

* 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

* 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

* fix: remove unnecessary dispose con config global update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Imanol Maiztegui
2026-03-17 15:59:52 +01:00
committed by GitHub
co-authored by github-actions[bot]
parent bea9eb6bf4
commit 28330ea765
47 changed files with 320 additions and 98 deletions
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ba76e2d5b993e999914fd0538650728adf78f7ad70978d8ee9982525f6ab5e8
size 17626
oid sha256:321afc41e5908e73656b1a8d5e3c32f23f54d10dee12655506b4b58348230305
size 13718
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:29872fa36a6a6ba9e20765f92380b86f9a91beb9702b3cdb95914ea46a2cf40c
size 17833
oid sha256:666d73c69b19e8c79815129b9d17747dbf755c316f3e6da0cba6977966b9cccc
size 13906
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce915e449cdec411c00df613ffc1017f95dba69737e0474fc8c483ea9f284026
size 17872
oid sha256:b0608142ccb5bab0468ae44060bed17c5ba98b8ee2d5a477627644d1c91e760a
size 13952
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b39675360da34fc6cb9c1ff4e51afeff79417ca4baa88816967d2b0a4ece858f
size 18043
oid sha256:7dd67617fdfc9ea6c4b0f54895d930d76c5c712b158e4be93967a3a3239a53b2
size 14143
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3b7d96f123e880b1395d160d17a48f385ef59344326c44d7cd13b342d2ca49e
size 24193
oid sha256:43b16bc81164f310ae40f7ebe6459281f374be323dfc07e3bef6b2d1608b6637
size 20835
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:739b8eb94e5cde39e4127a6ed3c499392174a380812e2e3497965643cac3dee9
size 22265
oid sha256:ee48a78cd2fe43c34d0014262ac794e48e5405d2116140d08219fc8e1621be0c
size 18516
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:62ef30b05bf2ec47a7ddd02a03f5f1cded5ced9cf9332dd3bd50d6809bcce96d
size 18447
oid sha256:f69b6545bd98003edf8c7208eb655066a24847e055cf0a5492c99ff67bbbab38
size 14621
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1f9864861136b578c4e46ece7dae5a139ada7e7be3d219462ef3a549b94239ec
size 18648
oid sha256:5a7db3c77ca7dee3a74fe3fd08b45e8d32a00193d1fb13ec7b4e2b1671dac7a3
size 14831
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:514803fa46f43ff58d8d403e630afc5c01a8841ac420d406755515bceb38beae
size 23132
oid sha256:c4aa38c3ade48132b3f0083438ef71c0e34f180db3bbd4d70455d6bb34a1225c
size 19202
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:578f6daa54a25fba38b049d623e2b12069b0cbe1c5e094031d8a0b0af0a56f52
size 23966
oid sha256:e46b79adada26121c948ce33b78ba78c8b0b13dc153708b60a56f1cfc6034503
size 20095
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:91ba11c0a81ced5dfe824a4c9e8fdcbaebaaddc660ef2b245df56903e180e4b7
size 18949
oid sha256:699b49cbb768c3a9cea64878a01b5507a1f304a14f12363f2329ad07c8cc35cb
size 15082
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6f8bc26191620881314eff9ac921abba4e201c5af7ec8d222e405fdee9ddae9a
size 17909
oid sha256:60e6d4b1d0e860195c26752e50a97e6d22d7911d029e7d08d24f46b327b21a01
size 14075
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce112e9762168271fd8b7b4316b740008269e9d248a668887adce3dde13478f0
size 18362
oid sha256:4ff80ac6cd76dbb278e6483c2e5c352095fc893ae5f5d5cb2eb70ded640bfc0e
size 16913
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d499d73467c37052d1277deefdd419e370a8239d7e117806d8442b2b9cc851a7
size 18886
oid sha256:6531e4d0e975a29d538bc7b609a987071afc0997b396fd06e52cbbf2b7ef2e26
size 17467
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3656d23c630a2d7f412d2fc3eee231f45cbe47e6d1e4e7ca44ae4cca3c3160b
size 15323
oid sha256:cb8dd7f0ecbbeed00e1321c050964b54f832a571c8acbc37269de205bd64a483
size 13788
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c95ebadbd707aab03b0b14858cd9e25c4e7b06e8976abf76e377b9eaa5bd2144
size 16900
oid sha256:484ec50824ececf29d8b1464fb94ef48637d4f97b9a3c68dcf3ae5d8f157dcf6
size 15502
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c43a0a2f6a351d53da542aba6b68113e8231c1ee9cf7276bf6a16263b65d811
size 18775
oid sha256:ab9edfc18d0d7094f7c5410d7b08e0012c2128f1638b7090aad9301f127473ed
size 17425
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d23b2a1621e33e92e65c7ead23b42f05decd443b370ef7122b840e6f5cf043e1
size 21177
oid sha256:05783f1a5b4c8631dee2582b243a724eb4fb811cde894f86ce3575e2da1a997e
size 17416
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:63b8651967a042ef6e79f721fc4bf99eae875296dbd991566d3c3b7e90f04669
size 18542
oid sha256:24364efcbb54ed842a9af0c630e6b2a4f5df37d8cabb651e18dcbebda37a26c5
size 17221
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:94ef29b3eca3b0e9e407daf9c17388dcf5735c134a74bc13500559bcf9707c9b
size 16338
oid sha256:e8e117613ac4378e82739c266c2e2f818e9ed79bc0e84e0ebe7b8f61c02b88c0
size 14836
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:168eacdc6523223587bad5f72f3828e03211733b4dc1fa39f152409089e03ff3
size 16468
oid sha256:2259e821d911a5455a4bdd72716d5ae1de1a5fd5b6aaad666d41846edac716cc
size 15079
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9c2402fb61b05c355cedea5a26c39f4a5b4d4a1e04c0fb8ac1ac50edbdc746b3
size 16426
oid sha256:3fc2414e502c0f064fc8d15e5f14ccdc5c250ded619b8362472d2a41fb522de9
size 15023
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:38712f08584902b1ccb267fed0271c6c1a9b5f549b47bd743ca344604df2095a
size 17433
oid sha256:acb0efba94927a8ada6a16a66bd82160a1f41d320a2f512114da54aaa2cc1574
size 16143
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:84e3f1c43bd36828f6548f3a3a85827693b370f6f3fc319408829eb742146bb5
size 19287
oid sha256:c21917a9f4f80354ac18a7c4ced1f204a1b28bed4ac5fb0cf34e339dbdc7d5da
size 15929
@@ -179,7 +179,6 @@ export const PermissionDock: Component<{
<div data-slot="permission-hint">{toolDescription()}</div>
</Show>
<p data-slot="permission-session-hint">{language.t("ui.permission.sessionHint")}</p>
<div data-slot="permission-actions">
<Button
variant="primary"
@@ -1377,14 +1377,6 @@
margin-top: 0;
}
[data-slot="permission-session-hint"] {
font-size: 11px;
color: var(--text-weak, var(--vscode-descriptionForeground));
margin: 0;
padding: 4px 0 0;
text-align: left;
}
[data-slot="permission-actions"] {
display: flex;
align-items: center;
@@ -158,12 +158,12 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
<Switch>
<Match when={props.request.always.length === 1 && props.request.always[0] === "*"}>
{/* kilocode_change */}
<TextBody title={"This will allow " + props.request.permission + " until Kilo is restarted."} />
<TextBody title={"This will allow " + props.request.permission + " permanently."} />
</Match>
<Match when={true}>
<box paddingLeft={1} gap={1}>
{/* kilocode_change */}
<text fg={theme.textMuted}>This will allow the following patterns until Kilo is restarted</text>
<text fg={theme.textMuted}>This will allow the following patterns permanently</text>
<box>
<For each={props.request.always}>
{(pattern) => (
+36 -9
View File
@@ -1426,7 +1426,7 @@ export namespace Config {
export async function update(config: Info) {
const filepath = path.join(Instance.directory, "config.json")
const existing = await loadFile(filepath)
await Filesystem.writeJson(filepath, stripNulls(mergeDeep(existing, config) as Record<string, unknown>)) // kilocode_change - strip null delete sentinels
await Filesystem.writeJson(filepath, mergeConfig(existing, config)) // kilocode_change
await Instance.dispose()
}
@@ -1462,6 +1462,35 @@ export namespace Config {
}
// kilocode_change end
// kilocode_change start — merge config with normalization pipeline
/**
* Merge a patch into an existing config:
* 1. Normalize permission scalars → objects when the patch has an object
* (e.g. existing `"bash": "ask"` + patch `"bash": { "npm *": "allow" }`
* → promotes existing to `"bash": { "*": "ask" }` so mergeDeep works)
* 2. Deep-merge
* 3. Strip null delete sentinels
*/
function mergeConfig(existing: Info, patch: Info): Info {
const e = { ...existing } as Record<string, unknown>
const p = patch as Record<string, unknown>
// Normalize permission scalars before merge (clone to avoid mutating the input)
const existingPerm = e.permission
const patchPerm = p.permission
if (isRecord(existingPerm) && isRecord(patchPerm)) {
const cloned = { ...existingPerm }
for (const [key, patchValue] of Object.entries(patchPerm)) {
const existingValue = cloned[key]
if (typeof existingValue === "string" && isRecord(patchValue)) {
cloned[key] = { "*": existingValue }
}
}
e.permission = cloned
}
return stripNulls(mergeDeep(e, p) as Record<string, unknown>) as Info
}
// kilocode_change end
function patchJsonc(input: string, patch: unknown, path: string[] = []): string {
if (!isRecord(patch)) {
// kilocode_change - null means "delete this key" — pass undefined to jsonc-parser's modify()
@@ -1478,11 +1507,15 @@ export namespace Config {
// scalar (e.g. permission.bash is "ask" as a string), jsonc-parser cannot
// add child keys to it. Detect this case and replace the whole node with
// the patch object in a single modify() call instead of recursing.
// For permission keys, promote the scalar to { "*": scalarValue } so the
// wildcard default is preserved. For other keys, replace directly.
if (path.length > 0) {
const tree = parseTree(input)
const node = tree && findNodeAtLocation(tree, path)
if (node && node.type !== "object") {
const edits = modify(input, path, patch, {
const isPermissionKey = path[0] === "permission" && path.length === 2
const replacement = isPermissionKey ? { "*": node.value, ...patch } : patch
const edits = modify(input, path, replacement, {
formattingOptions: { insertSpaces: true, tabSize: 2 },
})
return applyEdits(input, edits)
@@ -1540,7 +1573,7 @@ export namespace Config {
const next = await (async () => {
if (!filepath.endsWith(".jsonc")) {
const existing = parseConfig(before, filepath)
const merged = stripNulls(mergeDeep(existing, config) as Record<string, unknown>) as Info // kilocode_change - strip null delete sentinels
const merged = mergeConfig(existing, config) // kilocode_change
await Filesystem.writeJson(filepath, merged)
return merged
}
@@ -1553,11 +1586,6 @@ export namespace Config {
global.reset()
// kilocode_change start - only reset config cache, don't dispose all instances.
// Instance.disposeAll() was destroying all session state, MCP connections, and
// in-flight operations across every project whenever any global config changed
// (e.g. removing a mode). The cache reset above is sufficient — consumers will
// pick up the new config on their next read.
GlobalBus.emit("event", {
directory: "global",
payload: {
@@ -1565,7 +1593,6 @@ export namespace Config {
properties: {},
},
})
// kilocode_change end
return next
}
+44 -4
View File
@@ -65,6 +65,28 @@ export namespace PermissionNext {
return rulesets.flat()
}
// kilocode_change start — inverse of fromConfig: convert rules back to config format
export function toConfig(rules: Ruleset): Config.Permission {
const result: Config.Permission = {}
for (const rule of rules) {
const existing = result[rule.permission]
if (existing === undefined) {
// Always use object format to avoid replacing existing granular rules
// when merged via updateGlobal (e.g. { read: "allow" } would wipe
// { read: { "*": "ask", "src/*": "allow" } })
result[rule.permission] = { [rule.pattern]: rule.action }
continue
}
if (typeof existing === "string") {
result[rule.permission] = { "*": existing, [rule.pattern]: rule.action }
continue
}
existing[rule.pattern] = rule.action
}
return result
}
// kilocode_change end
export const Request = z
.object({
id: Identifier.schema("permission"),
@@ -175,11 +197,19 @@ export namespace PermissionNext {
const validRules = new Set(existing.info.metadata?.rules ?? [])
const permission = existing.info.permission
for (const pattern of input.approvedAlways ?? []) {
if (validRules.has(pattern)) s.approved.push({ permission, pattern, action: "allow" })
// Build rules in metadata.rules order so broader patterns come before
// specific ones, preserving intended precedence for evaluate(findLast).
const approvedSet = new Set(input.approvedAlways ?? [])
const deniedSet = new Set(input.deniedAlways ?? [])
const newRules: Ruleset = []
for (const pattern of existing.info.metadata?.rules ?? []) {
if (approvedSet.has(pattern)) newRules.push({ permission, pattern, action: "allow" })
if (deniedSet.has(pattern)) newRules.push({ permission, pattern, action: "deny" })
}
for (const pattern of input.deniedAlways ?? []) {
if (validRules.has(pattern)) s.approved.push({ permission, pattern, action: "deny" })
s.approved.push(...newRules)
if (newRules.length > 0) {
await Config.updateGlobal({ permission: toConfig(newRules) })
}
},
)
@@ -254,6 +284,16 @@ export namespace PermissionNext {
// UI to manage it
// db().insert(PermissionTable).values({ projectID: Instance.project.id, data: s.approved })
// .onConflictDoUpdate({ target: PermissionTable.projectID, set: { data: s.approved } }).run()
// kilocode_change start - persist always rules to global config
const alwaysRules: Ruleset = existing.info.always.map((pattern) => ({
permission: existing.info.permission,
pattern,
action: "allow" as const,
}))
if (alwaysRules.length > 0) {
await Config.updateGlobal({ permission: toConfig(alwaysRules) })
}
// kilocode_change end
return
}
},
@@ -166,4 +166,132 @@ describe("saveAlwaysRules", () => {
},
})
})
test("mixed allow/deny preserves metadata.rules order", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const askPromise = PermissionNext.ask({
id: "permission_5",
sessionID: "session_test",
permission: "bash",
patterns: ["npm install lodash"],
// rules ordered broad → specific
metadata: { rules: ["npm *", "npm install *"] },
always: ["npm install *"],
ruleset: [],
})
// Deny broad, allow specific — specific should win
await PermissionNext.saveAlwaysRules({
requestID: "permission_5",
approvedAlways: ["npm install *"],
deniedAlways: ["npm *"],
})
await PermissionNext.reply({ requestID: "permission_5", reply: "once" })
await expect(askPromise).resolves.toBeUndefined()
// "npm install foo" matches both rules; "npm install *" (allow) comes
// after "npm *" (deny) in metadata.rules order, so allow wins
const result = await PermissionNext.ask({
sessionID: "session_test",
permission: "bash",
patterns: ["npm install foo"],
metadata: {},
always: [],
ruleset: [],
})
expect(result).toBeUndefined()
},
})
})
test("deny broad + allow specific: specific allow wins", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const askPromise = PermissionNext.ask({
id: "permission_6",
sessionID: "session_test",
permission: "bash",
patterns: ["git log --oneline"],
metadata: { rules: ["git *", "git log *"] },
always: ["git log *"],
ruleset: [],
})
await PermissionNext.saveAlwaysRules({
requestID: "permission_6",
approvedAlways: ["git log *"],
deniedAlways: ["git *"],
})
await PermissionNext.reply({ requestID: "permission_6", reply: "once" })
await expect(askPromise).resolves.toBeUndefined()
// "git log --oneline" should be allowed (specific allow after broad deny)
const allowed = await PermissionNext.ask({
sessionID: "session_test",
permission: "bash",
patterns: ["git log --oneline"],
metadata: {},
always: [],
ruleset: [],
})
expect(allowed).toBeUndefined()
// "git status" should be denied (only matches broad deny)
await expect(
PermissionNext.ask({
sessionID: "session_test",
permission: "bash",
patterns: ["git status"],
metadata: {},
always: [],
ruleset: [],
}),
).rejects.toBeInstanceOf(PermissionNext.DeniedError)
},
})
})
test("rules not in metadata.rules are silently ignored", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const askPromise = PermissionNext.ask({
id: "permission_7",
sessionID: "session_test",
permission: "bash",
patterns: ["npm install"],
metadata: { rules: ["npm *"] },
always: ["npm *"],
ruleset: [],
})
// "curl" is not in metadata.rules — should be silently ignored
await PermissionNext.saveAlwaysRules({
requestID: "permission_7",
approvedAlways: ["npm *", "curl *"],
})
await PermissionNext.reply({ requestID: "permission_7", reply: "once" })
await expect(askPromise).resolves.toBeUndefined()
// curl should still require permission (not auto-allowed)
const curlPromise = PermissionNext.ask({
id: "permission_curl2",
sessionID: "session_test",
permission: "bash",
patterns: ["curl http://example.com"],
metadata: {},
always: [],
ruleset: [],
})
await PermissionNext.reply({ requestID: "permission_curl2", reply: "reject" })
await expect(curlPromise).rejects.toBeInstanceOf(PermissionNext.RejectedError)
},
})
})
})
@@ -0,0 +1,62 @@
import { test, expect } from "bun:test"
import { PermissionNext } from "../../src/permission/next"
// toConfig tests (inverse of fromConfig)
test("toConfig - single wildcard rule uses object format", () => {
const result = PermissionNext.toConfig([{ permission: "read", pattern: "*", action: "allow" }])
expect(result).toEqual({ read: { "*": "allow" } })
})
test("toConfig - single non-wildcard rule uses object format", () => {
const result = PermissionNext.toConfig([{ permission: "bash", pattern: "npm *", action: "allow" }])
expect(result).toEqual({ bash: { "npm *": "allow" } })
})
test("toConfig - multiple rules for same permission use object format", () => {
const result = PermissionNext.toConfig([
{ permission: "bash", pattern: "*", action: "ask" },
{ permission: "bash", pattern: "npm *", action: "allow" },
])
expect(result).toEqual({ bash: { "*": "ask", "npm *": "allow" } })
})
test("toConfig - mixed permissions", () => {
const result = PermissionNext.toConfig([
{ permission: "read", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "npm *", action: "allow" },
{ permission: "bash", pattern: "git *", action: "allow" },
])
expect(result).toEqual({
read: { "*": "allow" },
bash: { "npm *": "allow", "git *": "allow" },
})
})
test("toConfig - empty rules returns empty object", () => {
const result = PermissionNext.toConfig([])
expect(result).toEqual({})
})
test("toConfig - wildcard then specific promotes to object", () => {
const result = PermissionNext.toConfig([
{ permission: "bash", pattern: "*", action: "ask" },
{ permission: "bash", pattern: "rm *", action: "deny" },
])
expect(result).toEqual({ bash: { "*": "ask", "rm *": "deny" } })
})
test("toConfig - roundtrip with fromConfig (simple) always uses object format", () => {
const config = { read: "allow" as const, bash: "ask" as const }
const rules = PermissionNext.fromConfig(config)
const result = PermissionNext.toConfig(rules)
// toConfig always uses object format to avoid erasing existing granular rules on merge
expect(result).toEqual({ read: { "*": "allow" }, bash: { "*": "ask" } })
})
test("toConfig - roundtrip with fromConfig (object)", () => {
const config = { bash: { "*": "ask" as const, "npm *": "allow" as const, "git *": "allow" as const } }
const rules = PermissionNext.fromConfig(config)
const result = PermissionNext.toConfig(rules)
expect(result).toEqual(config)
})
-1
View File
@@ -120,7 +120,6 @@ export const dict = {
"ui.permission.deny": "رفض",
"ui.permission.allowAlways": "السماح دائمًا",
"ui.permission.allowOnce": "السماح مرة واحدة",
"ui.permission.sessionHint": '"السماح دائمًا" ينطبق على هذه الجلسة فقط. استخدم الإعدادات للأذونات العامة.',
"ui.message.expand": "توسيع الرسالة",
"ui.message.collapse": "طي الرسالة",
-2
View File
@@ -120,8 +120,6 @@ export const dict = {
"ui.permission.deny": "Negar",
"ui.permission.allowAlways": "Permitir sempre",
"ui.permission.allowOnce": "Permitir uma vez",
"ui.permission.sessionHint":
'"Permitir sempre" aplica-se apenas a esta sessão. Use as configurações para permissões globais.',
"ui.message.expand": "Expandir mensagem",
"ui.message.collapse": "Recolher mensagem",
-2
View File
@@ -124,8 +124,6 @@ export const dict = {
"ui.permission.deny": "Zabrani",
"ui.permission.allowAlways": "Uvijek dozvoli",
"ui.permission.allowOnce": "Dozvoli jednom",
"ui.permission.sessionHint":
'"Uvijek dozvoli" primjenjuje se samo na ovu sesiju. Koristite postavke za globalne dozvole.',
"ui.message.expand": "Proširi poruku",
"ui.message.collapse": "Sažmi poruku",
-2
View File
@@ -119,8 +119,6 @@ export const dict = {
"ui.permission.deny": "Afvis",
"ui.permission.allowAlways": "Tillad altid",
"ui.permission.allowOnce": "Tillad én gang",
"ui.permission.sessionHint":
'"Tillad altid" gælder kun for denne session. Brug indstillinger til globale tilladelser.',
"ui.message.expand": "Udvid besked",
"ui.message.collapse": "Skjul besked",
-2
View File
@@ -125,8 +125,6 @@ export const dict = {
"ui.permission.deny": "Verweigern",
"ui.permission.allowAlways": "Immer erlauben",
"ui.permission.allowOnce": "Einmal erlauben",
"ui.permission.sessionHint":
'"Immer erlauben" gilt nur für diese Sitzung. Globale Berechtigungen in den Einstellungen ändern.',
"ui.message.expand": "Nachricht erweitern",
"ui.message.collapse": "Nachricht reduzieren",
-1
View File
@@ -121,7 +121,6 @@ export const dict: Record<string, string> = {
"ui.permission.deny": "Deny",
"ui.permission.allowAlways": "Allow always",
"ui.permission.allowOnce": "Allow once",
"ui.permission.sessionHint": '"Allow always" applies to this session only. Use settings for global permissions.',
"ui.message.expand": "Expand message",
"ui.message.collapse": "Collapse message",
-2
View File
@@ -120,8 +120,6 @@ export const dict = {
"ui.permission.deny": "Denegar",
"ui.permission.allowAlways": "Permitir siempre",
"ui.permission.allowOnce": "Permitir una vez",
"ui.permission.sessionHint":
'"Permitir siempre" se aplica solo a esta sesión. Usa la configuración para permisos globales.',
"ui.message.expand": "Expandir mensaje",
"ui.message.collapse": "Colapsar mensaje",
-2
View File
@@ -120,8 +120,6 @@ export const dict = {
"ui.permission.deny": "Refuser",
"ui.permission.allowAlways": "Toujours autoriser",
"ui.permission.allowOnce": "Autoriser une fois",
"ui.permission.sessionHint":
'"Toujours autoriser" s\'applique uniquement à cette session. Utilisez les paramètres pour les autorisations globales.',
"ui.message.expand": "Développer le message",
"ui.message.collapse": "Réduire le message",
-2
View File
@@ -119,8 +119,6 @@ export const dict = {
"ui.permission.deny": "拒否",
"ui.permission.allowAlways": "常に許可",
"ui.permission.allowOnce": "今回のみ許可",
"ui.permission.sessionHint":
"「常に許可」はこのセッションにのみ適用されます。グローバルな権限は設定で変更してください。",
"ui.message.expand": "メッセージを展開",
"ui.message.collapse": "メッセージを折りたたむ",
-1
View File
@@ -120,7 +120,6 @@ export const dict = {
"ui.permission.deny": "거부",
"ui.permission.allowAlways": "항상 허용",
"ui.permission.allowOnce": "한 번만 허용",
"ui.permission.sessionHint": '"항상 허용"은 현재 세션에만 적용됩니다. 전역 권한은 설정에서 변경하세요.',
"ui.message.expand": "메시지 펼치기",
"ui.message.collapse": "메시지 접기",
-2
View File
@@ -123,8 +123,6 @@ export const dict: Record<Keys, string> = {
"ui.permission.deny": "Avslå",
"ui.permission.allowAlways": "Tillat alltid",
"ui.permission.allowOnce": "Tillat én gang",
"ui.permission.sessionHint":
'"Tillat alltid" gjelder bare for denne økten. Bruk innstillinger for globale tillatelser.',
"ui.message.expand": "Utvid melding",
"ui.message.collapse": "Skjul melding",
-2
View File
@@ -119,8 +119,6 @@ export const dict = {
"ui.permission.deny": "Odmów",
"ui.permission.allowAlways": "Zezwalaj zawsze",
"ui.permission.allowOnce": "Zezwól raz",
"ui.permission.sessionHint":
'"Zezwalaj zawsze" dotyczy tylko tej sesji. Użyj ustawień, aby zmienić globalne uprawnienia.',
"ui.message.expand": "Rozwiń wiadomość",
"ui.message.collapse": "Zwiń wiadomość",
-2
View File
@@ -119,8 +119,6 @@ export const dict = {
"ui.permission.deny": "Запретить",
"ui.permission.allowAlways": "Разрешить всегда",
"ui.permission.allowOnce": "Разрешить один раз",
"ui.permission.sessionHint":
"«Разрешить всегда» применяется только к текущей сессии. Для глобальных разрешений используйте настройки.",
"ui.message.expand": "Развернуть сообщение",
"ui.message.collapse": "Свернуть сообщение",
-1
View File
@@ -121,7 +121,6 @@ export const dict = {
"ui.permission.deny": "ปฏิเสธ",
"ui.permission.allowAlways": "อนุญาตเสมอ",
"ui.permission.allowOnce": "อนุญาตครั้งเดียว",
"ui.permission.sessionHint": '"อนุญาตเสมอ" ใช้ได้เฉพาะในเซสชันนี้เท่านั้น ใช้การตั้งค่าสำหรับสิทธิ์ส่วนกลาง',
"ui.message.expand": "ขยายข้อความ",
"ui.message.collapse": "ย่อข้อความ",
-1
View File
@@ -124,7 +124,6 @@ export const dict = {
"ui.permission.deny": "拒绝",
"ui.permission.allowAlways": "始终允许",
"ui.permission.allowOnce": "允许一次",
"ui.permission.sessionHint": '"始终允许" 仅适用于本次会话。如需全局权限设置,请使用设置。',
"ui.message.expand": "展开消息",
"ui.message.collapse": "收起消息",
-1
View File
@@ -124,7 +124,6 @@ export const dict = {
"ui.permission.deny": "拒絕",
"ui.permission.allowAlways": "永遠允許",
"ui.permission.allowOnce": "允許一次",
"ui.permission.sessionHint": '"永遠允許" 僅適用於此工作階段。如需全域權限設定,請使用設定。',
"ui.message.expand": "展開訊息",
"ui.message.collapse": "收合訊息",