feat(core): add agents and permissions config schema

This commit is contained in:
Dax Raad 2026-05-28 01:43:23 -04:00
commit d9b4f7b84d
6 changed files with 141 additions and 8 deletions

View file

@ -6,8 +6,10 @@ import { Context, Effect, Layer, Option, Schema } from "effect"
import { AppFileSystem } from "./filesystem"
import { Global } from "./global"
import { Location } from "./location"
import { PermissionV2 } from "./permission"
import { Policy } from "./policy"
import { AbsolutePath } from "./schema"
import { ConfigAgent } from "./config/agent"
import { ConfigAttachments } from "./config/attachments"
import { ConfigExperimental } from "./config/experimental"
import { ConfigFormatter } from "./config/formatter"
@ -42,6 +44,12 @@ export class Info extends Schema.Class<Info>("Config.Info")({
username: Schema.String.pipe(Schema.optional).annotate({
description: "Username displayed in conversations and used for telemetry identity",
}),
permissions: PermissionV2.Ruleset.pipe(Schema.optional).annotate({
description: "Ordered tool permission rules applied to agent tool use",
}),
agents: Schema.Record(Schema.String, ConfigAgent.Info).pipe(Schema.optional).annotate({
description: "Named built-in agent overrides and custom agent definitions",
}),
snapshots: Schema.Boolean.pipe(Schema.optional).annotate({
description: "Enable snapshots used for undo and revert behavior",
}),

View file

@ -0,0 +1,25 @@
export * as ConfigAgent from "./agent"
import { Schema } from "effect"
import { PermissionV2 } from "../permission"
import { ProviderV2 } from "../provider"
import { PositiveInt } from "../schema"
export const Color = Schema.Union([
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
])
export class Info extends Schema.Class<Info>("ConfigV2.Agent")({
model: Schema.String.pipe(Schema.optional),
variant: Schema.String.pipe(Schema.optional),
options: ProviderV2.Options.pipe(Schema.optional),
system: Schema.String.pipe(Schema.optional),
description: Schema.String.pipe(Schema.optional),
mode: Schema.Literals(["subagent", "primary", "all"]).pipe(Schema.optional),
hidden: Schema.Boolean.pipe(Schema.optional),
color: Color.pipe(Schema.optional),
steps: PositiveInt.pipe(Schema.optional),
disabled: Schema.Boolean.pipe(Schema.optional),
permissions: PermissionV2.Ruleset.pipe(Schema.optional),
}) {}

View file

@ -22,7 +22,7 @@ class Model extends Schema.Class<Model>("ConfigV2.Model")({
...ProviderV2.Options.fields,
}).pipe(Schema.Array, Schema.optional),
cost: ModelV2.Cost.pipe(Schema.Array).pipe(Schema.optional),
enabled: Schema.Boolean.pipe(Schema.optional),
disabled: Schema.Boolean.pipe(Schema.optional),
limit: Schema.Struct({
context: Schema.Int,
input: Schema.Int.pipe(Schema.optional),
@ -110,7 +110,7 @@ export const Plugin = PluginV2.define({
cache: { ...cost.cache },
}))
}
if (config.enabled !== undefined) model.enabled = config.enabled
if (config.disabled !== undefined) model.enabled = !config.disabled
if (config.limit !== undefined) model.limit = { ...config.limit }
})
}

View file

@ -171,6 +171,29 @@ describe("Config", () => {
share: "disabled",
enterprise: { url: "https://share.example.com" },
username: "test-user",
permissions: [
{ permission: "bash", pattern: "*", action: "ask" },
{ permission: "bash", pattern: "git status", action: "allow" },
],
agents: {
reviewer: {
model: "openrouter/openai/gpt-5",
variant: "high",
options: {
headers: { "x-agent": "reviewer" },
body: {},
aisdk: { provider: {}, request: { reasoningEffort: "high" } },
},
description: "Review changes for correctness",
system: "Find regressions.",
mode: "subagent",
hidden: false,
color: "warning",
steps: 12,
disabled: false,
permissions: [{ permission: "edit", pattern: "*", action: "deny" }],
},
},
snapshots: false,
watcher: { ignore: ["node_modules/**", "dist/**", ".git"] },
formatter: { prettier: { disabled: true }, custom: { command: ["custom-fmt", "$FILE"], extensions: [".foo"] } },
@ -203,6 +226,27 @@ describe("Config", () => {
expect(documents[0]?.info.share).toBe("disabled")
expect(documents[0]?.info.enterprise).toEqual({ url: "https://share.example.com" })
expect(documents[0]?.info.username).toBe("test-user")
expect(documents[0]?.info.permissions).toEqual([
{ permission: "bash", pattern: "*", action: "ask" },
{ permission: "bash", pattern: "git status", action: "allow" },
])
expect(documents[0]?.info.agents?.reviewer).toEqual({
model: "openrouter/openai/gpt-5",
variant: "high",
options: {
headers: { "x-agent": "reviewer" },
body: {},
aisdk: { provider: {}, request: { reasoningEffort: "high" } },
},
description: "Review changes for correctness",
system: "Find regressions.",
mode: "subagent",
hidden: false,
color: "warning",
steps: 12,
disabled: false,
permissions: [{ permission: "edit", pattern: "*", action: "deny" }],
})
expect(documents[0]?.info.snapshots).toBe(false)
expect(documents[0]?.info.watcher).toEqual({ ignore: ["node_modules/**", "dist/**", ".git"] })
expect(documents[0]?.info.formatter).toEqual({

View file

@ -45,7 +45,7 @@ describe("ConfigProvider.Plugin", () => {
chat: {
name: "First",
capabilities: { tools: true, input: ["text"], output: ["text"] },
enabled: false,
disabled: true,
limit: { context: 100, output: 50 },
options: options({ first: "first", shared: "first" }, "retained"),
variants: [