feat(core): add runtime and sharing config schema
This commit is contained in:
@@ -8,10 +8,14 @@ import { Global } from "./global"
|
||||
import { Location } from "./location"
|
||||
import { Policy } from "./policy"
|
||||
import { AbsolutePath } from "./schema"
|
||||
import { ConfigAttachments } from "./config/attachments"
|
||||
import { ConfigExperimental } from "./config/experimental"
|
||||
import { ConfigFormatter } from "./config/formatter"
|
||||
import { ConfigLSP } from "./config/lsp"
|
||||
import { ConfigPlugin } from "./config/plugin"
|
||||
import { ConfigProvider } from "./config/provider"
|
||||
import { ConfigReference } from "./config/reference"
|
||||
import { ConfigToolOutput } from "./config/tool-output"
|
||||
import { ConfigWatcher } from "./config/watcher"
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Info")({
|
||||
@@ -27,12 +31,35 @@ export class Info extends Schema.Class<Info>("Config.Info")({
|
||||
autoupdate: Schema.Union([Schema.Boolean, Schema.Literal("notify")]).pipe(Schema.optional).annotate({
|
||||
description: "Automatically update or notify when a new version is available",
|
||||
}),
|
||||
share: Schema.Literals(["manual", "auto", "disabled"]).pipe(Schema.optional).annotate({
|
||||
description: "Control whether sessions may be shared manually, automatically, or not at all",
|
||||
}),
|
||||
enterprise: Schema.Struct({
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
}).pipe(Schema.optional).annotate({
|
||||
description: "Enterprise sharing service configuration",
|
||||
}),
|
||||
username: Schema.String.pipe(Schema.optional).annotate({
|
||||
description: "Username displayed in conversations and used for telemetry identity",
|
||||
}),
|
||||
snapshots: Schema.Boolean.pipe(Schema.optional).annotate({
|
||||
description: "Enable snapshots used for undo and revert behavior",
|
||||
}),
|
||||
watcher: ConfigWatcher.Info.pipe(Schema.optional).annotate({
|
||||
description: "Filesystem watcher configuration",
|
||||
}),
|
||||
formatter: ConfigFormatter.Info.pipe(Schema.optional).annotate({
|
||||
description: "Enable built-in formatters or configure formatter overrides",
|
||||
}),
|
||||
lsp: ConfigLSP.Info.pipe(Schema.optional).annotate({
|
||||
description: "Enable built-in language servers or configure server overrides",
|
||||
}),
|
||||
attachments: ConfigAttachments.Info.pipe(Schema.optional).annotate({
|
||||
description: "Attachment processing configuration",
|
||||
}),
|
||||
tool_output: ConfigToolOutput.Info.pipe(Schema.optional).annotate({
|
||||
description: "Tool output truncation thresholds",
|
||||
}),
|
||||
skills: Schema.String.pipe(Schema.Array, Schema.optional).annotate({
|
||||
description: "Additional paths or URLs to discover skills from",
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export * as ConfigAttachments from "./attachments"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "../schema"
|
||||
|
||||
export class Image extends Schema.Class<Image>("ConfigV2.Attachments.Image")({
|
||||
auto_resize: Schema.Boolean.pipe(Schema.optional),
|
||||
max_width: PositiveInt.pipe(Schema.optional),
|
||||
max_height: PositiveInt.pipe(Schema.optional),
|
||||
max_base64_bytes: PositiveInt.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export class Info extends Schema.Class<Info>("ConfigV2.Attachments")({
|
||||
image: Image.pipe(Schema.optional),
|
||||
}) {}
|
||||
@@ -8,11 +8,11 @@ import { Policy as PolicyV2 } from "../policy"
|
||||
// this union makes it valid in authored config while keeping Policy generic.
|
||||
export const PolicyAction = Schema.Union([Catalog.PolicyActions])
|
||||
|
||||
export class Policy extends Schema.Class<Policy>("Config.Experimental.Policy")({
|
||||
export class Policy extends Schema.Class<Policy>("ConfigV2.Experimental.Policy")({
|
||||
...PolicyV2.Info.fields,
|
||||
action: PolicyAction,
|
||||
}) {}
|
||||
|
||||
export class Experimental extends Schema.Class<Experimental>("Config.Experimental")({
|
||||
export class Experimental extends Schema.Class<Experimental>("ConfigV2.Experimental")({
|
||||
policies: Policy.pipe(Schema.Array, Schema.optional),
|
||||
}) {}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export * as ConfigFormatter from "./formatter"
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export class Entry extends Schema.Class<Entry>("ConfigV2.Formatter.Entry")({
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
command: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
environment: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
extensions: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
}) {}
|
||||
|
||||
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)])
|
||||
@@ -0,0 +1,18 @@
|
||||
export * as ConfigLSP from "./lsp"
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const Disabled = Schema.Struct({
|
||||
disabled: Schema.Literal(true),
|
||||
})
|
||||
|
||||
export class Server extends Schema.Class<Server>("ConfigV2.LSP.Server")({
|
||||
command: Schema.String.pipe(Schema.Array),
|
||||
extensions: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
env: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
initialization: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export const Entry = Schema.Union([Disabled, Server])
|
||||
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)])
|
||||
@@ -2,7 +2,7 @@ export * as ConfigPlugin from "./plugin"
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export class Entry extends Schema.Class<Entry>("Config.Plugin.Entry")({
|
||||
export class Entry extends Schema.Class<Entry>("ConfigV2.Plugin.Entry")({
|
||||
package: Schema.String,
|
||||
options: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
@@ -2,12 +2,12 @@ export * as ConfigReference from "./reference"
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export class Git extends Schema.Class<Git>("Config.Reference.Git")({
|
||||
export class Git extends Schema.Class<Git>("ConfigV2.Reference.Git")({
|
||||
repository: Schema.String,
|
||||
branch: Schema.String.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export class Local extends Schema.Class<Local>("Config.Reference.Local")({
|
||||
export class Local extends Schema.Class<Local>("ConfigV2.Reference.Local")({
|
||||
path: Schema.String,
|
||||
}) {}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export * as ConfigToolOutput from "./tool-output"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "../schema"
|
||||
|
||||
export class Info extends Schema.Class<Info>("ConfigV2.ToolOutput")({
|
||||
max_lines: PositiveInt.pipe(Schema.optional),
|
||||
max_bytes: PositiveInt.pipe(Schema.optional),
|
||||
}) {}
|
||||
@@ -2,6 +2,6 @@ export * as ConfigWatcher from "./watcher"
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Watcher")({
|
||||
export class Info extends Schema.Class<Info>("ConfigV2.Watcher")({
|
||||
ignore: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
}) {}
|
||||
|
||||
@@ -168,8 +168,15 @@ describe("Config", () => {
|
||||
shell: "/bin/bash",
|
||||
model: "anthropic/claude",
|
||||
autoupdate: "notify",
|
||||
share: "disabled",
|
||||
enterprise: { url: "https://share.example.com" },
|
||||
username: "test-user",
|
||||
snapshots: false,
|
||||
watcher: { ignore: ["node_modules/**", "dist/**", ".git"] },
|
||||
formatter: { prettier: { disabled: true }, custom: { command: ["custom-fmt", "$FILE"], extensions: [".foo"] } },
|
||||
lsp: { typescript: { disabled: true }, custom: { command: ["custom-lsp"], extensions: [".foo"] } },
|
||||
attachments: { image: { auto_resize: false, max_width: 1200, max_height: 900, max_base64_bytes: 1048576 } },
|
||||
tool_output: { max_lines: 1000, max_bytes: 32768 },
|
||||
skills: ["./skills", "~/shared-skills", "https://example.com/.well-known/skills/"],
|
||||
instructions: ["CONTRIBUTING.md", ".cursor/rules/*.md", "https://example.com/shared-rules.md"],
|
||||
references: {
|
||||
@@ -193,8 +200,23 @@ describe("Config", () => {
|
||||
expect(documents[0]?.info.shell).toBe("/bin/bash")
|
||||
expect(documents[0]?.info.model).toBe("anthropic/claude")
|
||||
expect(documents[0]?.info.autoupdate).toBe("notify")
|
||||
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.snapshots).toBe(false)
|
||||
expect(documents[0]?.info.watcher).toEqual({ ignore: ["node_modules/**", "dist/**", ".git"] })
|
||||
expect(documents[0]?.info.formatter).toEqual({
|
||||
prettier: { disabled: true },
|
||||
custom: { command: ["custom-fmt", "$FILE"], extensions: [".foo"] },
|
||||
})
|
||||
expect(documents[0]?.info.lsp).toEqual({
|
||||
typescript: { disabled: true },
|
||||
custom: { command: ["custom-lsp"], extensions: [".foo"] },
|
||||
})
|
||||
expect(documents[0]?.info.attachments).toEqual({
|
||||
image: { auto_resize: false, max_width: 1200, max_height: 900, max_base64_bytes: 1048576 },
|
||||
})
|
||||
expect(documents[0]?.info.tool_output).toEqual({ max_lines: 1000, max_bytes: 32768 })
|
||||
expect(documents[0]?.info.skills).toEqual([
|
||||
"./skills",
|
||||
"~/shared-skills",
|
||||
|
||||
+41
-8
@@ -110,10 +110,31 @@ Settings controlling local file observation, snapshots, language tooling, and to
|
||||
| ------------- | --------------------------------------- | ------- | ----- |
|
||||
| `watcher` | Ignore patterns for filesystem watching | keep | Keep `{ ignore?: string[] }`; this configures the filesystem watcher subsystem. |
|
||||
| `snapshot` | Enable filesystem snapshot tracking | redesign | Rename to plural `snapshots`; controls creation of snapshots used for undo and revert behavior. |
|
||||
| `formatter` | Configure formatters | pending | |
|
||||
| `lsp` | Configure language servers | pending | |
|
||||
| `attachment` | Configure attachment/image processing | pending | |
|
||||
| `tool_output` | Configure tool output truncation limits | pending | |
|
||||
| `formatter` | Configure formatters | keep | Keep singular `boolean \| Record<string, entry>` shape; it configures built-in enablement and named formatter overrides. |
|
||||
| `lsp` | Configure language servers | keep | Keep singular `boolean \| Record<string, entry>` shape; custom servers need commands and file extensions. |
|
||||
| `attachment` | Configure attachment/image processing | redesign | Rename to plural `attachments`; retain `{ image?: { auto_resize?, max_width?, max_height?, max_base64_bytes? } }` for input normalization limits. |
|
||||
| `tool_output` | Configure tool output truncation limits | keep | Keep `{ max_lines?, max_bytes? }`; both positive thresholds apply to saved-preview truncation behavior. |
|
||||
|
||||
`formatter` and `lsp` configure one project tooling subsystem each, so their singular names remain appropriate. `true` enables the built-in registrations, `false` disables them, and a keyed object enables built-ins while applying named overrides or custom registrations. Custom language servers must declare `extensions` so runtime file attachment is deterministic; validation of known built-in server IDs belongs with the eventual v2 LSP integration rather than the aggregate core config schema.
|
||||
|
||||
Rename legacy `attachment` to `attachments` in v2. This setting controls processing for the attachment domain and may expand beyond image handling, while singular `attachment` is already used as a model capability flag indicating whether one model accepts attachments.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"formatter": {
|
||||
"prettier": { "disabled": true },
|
||||
"project": { "command": ["./scripts/format", "$FILE"], "extensions": [".foo"] },
|
||||
},
|
||||
"lsp": {
|
||||
"typescript": { "disabled": true },
|
||||
"project": { "command": ["project-language-server", "--stdio"], "extensions": [".foo"] },
|
||||
},
|
||||
"attachments": {
|
||||
"image": { "auto_resize": true, "max_width": 2000, "max_height": 2000 },
|
||||
},
|
||||
"tool_output": { "max_lines": 2000, "max_bytes": 51200 },
|
||||
}
|
||||
```
|
||||
|
||||
## Group 6: Sharing And Identity
|
||||
|
||||
@@ -121,10 +142,22 @@ Settings affecting sharing behavior or user/account identity rather than model e
|
||||
|
||||
| Field | Current Purpose | Status | Notes |
|
||||
| ------------ | ----------------------------------------------- | ------- | ------------------------------- |
|
||||
| `share` | Session sharing behavior | pending | |
|
||||
| `autoshare` | Legacy automatic sharing flag | pending | Deprecated in favor of `share`. |
|
||||
| `enterprise` | Enterprise URL configuration | pending | |
|
||||
| `username` | Display username in conversations and telemetry | pending | |
|
||||
| `share` | Session sharing behavior | keep | Keep `"manual" \| "auto" \| "disabled"`; it controls manual sharing permission and automatic sharing of new sessions. |
|
||||
| `autoshare` | Legacy automatic sharing flag | remove | Do not port deprecated alias; use `share: "auto"`. |
|
||||
| `enterprise` | Enterprise URL configuration | keep | Keep `{ url?: string }`; currently selects the legacy sharing service endpoint when no organization account is active. |
|
||||
| `username` | Display username in conversations and telemetry | keep | Keep string identity override; runtime may otherwise resolve an operating-system username. |
|
||||
|
||||
Retain `share` as the single session-sharing setting. `"manual"` permits explicit sharing, `"auto"` shares newly created top-level sessions, and `"disabled"` prevents sharing. Legacy `autoshare: true` is only an alias for `share: "auto"`, so v2 does not expose it.
|
||||
|
||||
Retain `enterprise.url` for legacy enterprise share hosting selection and `username` as a user-facing identity override. These remain separate from server authentication credentials; `username` identifies the user in conversation and telemetry behavior rather than HTTP basic-auth configuration.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"share": "disabled",
|
||||
"enterprise": { "url": "https://share.example.com" },
|
||||
"username": "developer",
|
||||
}
|
||||
```
|
||||
|
||||
## Group 7: Providers And Model Selection
|
||||
|
||||
|
||||
Reference in New Issue
Block a user