From 7784b3ee0d033da3eab101e45882387f4a3e1eaa Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 13 Sep 2026 19:11:13 -0400 Subject: [PATCH] refactor(core): isolate compatibility skills --- V2_HTTP_API_AUDIT.md | 2 +- .../client/src/promise/generated/types.ts | 2 - packages/core/src/config.ts | 48 +++++++----- .../core/src/config/plugin/compatibility.ts | 78 +++++++++++++++++++ packages/core/src/config/plugin/skill.ts | 5 -- packages/core/src/plugin/internal.ts | 2 + packages/core/test/config/config.test.ts | 31 +++----- packages/core/test/config/skill.test.ts | 43 ++++++---- packages/protocol/openapi.json | 34 -------- packages/schema/src/config.ts | 12 +-- packages/schema/test/config.test.ts | 4 +- services/www/openapi.json | 34 -------- services/www/public/openapi.json | 34 -------- 13 files changed, 150 insertions(+), 179 deletions(-) create mode 100644 packages/core/src/config/plugin/compatibility.ts diff --git a/V2_HTTP_API_AUDIT.md b/V2_HTTP_API_AUDIT.md index 99313c7f0bd..1f3e26fd06b 100644 --- a/V2_HTTP_API_AUDIT.md +++ b/V2_HTTP_API_AUDIT.md @@ -97,7 +97,7 @@ Review endpoints in document order. For each endpoint, select one disposition an | [x] 018 | `GET` | `/api/command` | `command.list` | Keep | Request and response accepted as-is. | | [x] 019 | `GET` | `/api/skill` | `skill.list` | Keep | Renamed `location` to `path`; removed the skill-specific `slash` flag and slash-command behavior. | | [x] 020 | `GET` | `/api/reference` | `reference.list` | Keep | Removed duplicate `description` and `hidden` fields from nested `source`. | -| [ ] 021 | `GET` | `/api/config` | `config.get` | | | +| [ ] 021 | `GET` | `/api/config` | `config.get` | Change | Removed `claude` and `agents` entry variants; one compatibility plugin now normalizes their skills. | | [ ] 022 | `GET` | `/api/config/preferences` | `config.preferences` | | | | [ ] 023 | `PATCH` | `/api/config/preferences` | `config.updatePreferences` | | | | [ ] 024 | `GET` | `/api/config/shell` | `config.shells` | | | diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 9936661fe79..1f4b013b4b1 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -2092,8 +2092,6 @@ export type ConfigEntry = } } | { type: "directory"; path: string } - | { type: "agents"; path: string } - | { type: "claude"; path: string } export type SessionInboxUser = { id: string diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 5ad679a99b7..e1694459ded 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -5,17 +5,7 @@ import path from "path" import { isDeepStrictEqual } from "node:util" import { applyEdits, modify, type ParseError, parse } from "jsonc-parser" import { Context, Effect, FiberMap, Layer, Option, PubSub, Ref, Schema, Semaphore, Stream } from "effect" -import { - AgentsDirectory, - ClaudeDirectory, - Directory, - Document, - Info, - type Preferences, - type PreferencesPatch, - type Entry, - Event, -} from "@opencode/schema/config" +import { Directory, Document, Info, type Preferences, type PreferencesPatch, type Entry, Event } from "@opencode/schema/config" import { Credential } from "./credential.js" import { Bus } from "./bus.js" import { Watcher } from "./filesystem/watcher.js" @@ -37,6 +27,11 @@ export function latest(entries: readonly Entry[], key: K): export interface Interface { /** Returns location config documents and discovery sources from lowest to highest priority. */ readonly entries: () => Effect.Effect + /** Compatibility roots consumed by internal compatibility plugins. */ + readonly compatibility?: () => Effect.Effect<{ + readonly claude: readonly AbsolutePath[] + readonly agents: readonly AbsolutePath[] + }> /** * Streams raw filesystem updates under config roots. Config owns root * topology and watch reconciliation; domain owners filter this feed for the @@ -71,13 +66,20 @@ export interface TestInterface extends Interface { export class Test extends Context.Service()("@opencode/Config/Test") {} /** In-memory config for tests: static entries with replaceable state and a test-driven change feed. */ -export const testLayer = (initial: Entry[] = []) => +export const testLayer = ( + initial: Entry[] = [], + compatibility: { readonly claude: readonly AbsolutePath[]; readonly agents: readonly AbsolutePath[] } = { + claude: [], + agents: [], + }, +) => Layer.effectContext( Effect.gen(function* () { const entries = yield* Ref.make(initial) const updates = yield* PubSub.unbounded() const service = Test.of({ entries: () => Ref.get(entries), + compatibility: () => Effect.succeed(compatibility), changes: () => Stream.fromPubSub(updates), setEntries: (next) => Ref.set(entries, next), emitChange: (update) => PubSub.publish(updates, update).pipe(Effect.asVoid), @@ -205,8 +207,6 @@ export const layer = (options?: Options) => }) const load = Effect.fn("Config.load")(function* (sources: ConfigDiscovery.Sources) { - const claude = yield* Effect.filter(sources.claude, (path) => fs.isDir(path)) - const agents = yield* Effect.filter(sources.agents, (path) => fs.isDir(path)) const direct = yield* Effect.forEach(sources.direct, (filepath) => loadFile(filepath)).pipe( Effect.orDie, Effect.map((entries) => entries.filter((entry): entry is Document => entry !== undefined)), @@ -244,8 +244,6 @@ export const layer = (options?: Options) => ) return [ ...(yield* loadWellknown().pipe(Effect.orDie)), - ...claude.map((path) => new ClaudeDirectory({ type: "claude", path })), - ...agents.map((path) => new AgentsDirectory({ type: "agents", path })), ...globalSupplementary, ...explicit, ...direct, @@ -255,6 +253,7 @@ export const layer = (options?: Options) => }) const initial = yield* ConfigDiscovery.discover(options) + let sources = initial let configs = yield* load(initial) const updates = yield* PubSub.unbounded() const reloads = yield* PubSub.sliding(1) @@ -280,10 +279,14 @@ export const layer = (options?: Options) => const reload = Effect.fn("Config.reload")( function* () { - const sources = yield* ConfigDiscovery.discover(options) - const next = yield* load(sources) - yield* reconcile(sources) - if (isDeepStrictEqual(configs, next)) return + const discovered = yield* ConfigDiscovery.discover(options) + const next = yield* load(discovered) + yield* reconcile(discovered) + const compatibilityChanged = + !isDeepStrictEqual(sources.claude, discovered.claude) || + !isDeepStrictEqual(sources.agents, discovered.agents) + if (isDeepStrictEqual(configs, next) && !compatibilityChanged) return + sources = discovered configs = next yield* bus.publish(Event.Updated, {}) }, @@ -379,6 +382,11 @@ export const layer = (options?: Options) => entries: Effect.fnUntraced(function* () { return configs }), + compatibility: () => + Effect.all({ + claude: Effect.filter(sources.claude, fs.isDir), + agents: Effect.filter(sources.agents, fs.isDir), + }), changes: () => Stream.fromPubSub(updates), preferences, updatePreferences, diff --git a/packages/core/src/config/plugin/compatibility.ts b/packages/core/src/config/plugin/compatibility.ts new file mode 100644 index 00000000000..c25cbce42df --- /dev/null +++ b/packages/core/src/config/plugin/compatibility.ts @@ -0,0 +1,78 @@ +export * as ConfigCompatibilityPlugin from "./compatibility.js" + +import { define } from "@opencode/plugin/effect/plugin" +import { FSUtil } from "@opencode/util/fs-util" +import path from "path" +import { Effect, FiberMap, PubSub, Semaphore, Stream } from "effect" +import { Config } from "../../config.js" +import { Watcher } from "../../filesystem/watcher.js" +import { AbsolutePath } from "../../schema.js" +import { Skill } from "../../skill.js" +import { SkillFile } from "./skill-file.js" + +export const Plugin = define({ + id: "opencode.config.compatibility", + effect: Effect.fn(function* (ctx) { + const config = yield* Config.Service + const fs = yield* FSUtil.Service + const watcher = yield* Watcher.Service + const watches = yield* FiberMap.make() + const changes = yield* PubSub.sliding(1) + const lock = Semaphore.makeUnsafe(1) + const skills: Skill.Info[] = [] + + const watch = Effect.fn("ConfigCompatibilityPlugin.watch")(function* ( + target: string, + type: "file" | "directory", + ) { + const updates = yield* watcher.subscribe({ path: target, type }) + yield* FiberMap.run( + watches, + `${type}:${target}`, + updates.pipe(Stream.runForEach((update) => PubSub.publish(changes, update.path).pipe(Effect.asVoid))), + { onlyIfMissing: true, startImmediately: true }, + ) + }) + + const refresh = Effect.fn("ConfigCompatibilityPlugin.refresh")( + function* () { + yield* FiberMap.clear(watches) + const roots = config.compatibility ? yield* config.compatibility() : { claude: [], agents: [] } + const directories = [...roots.claude, ...roots.agents].map((root) => path.join(root, "skills")) + const loaded = new Map() + for (const directory of directories) { + const resolved = yield* fs.realPath(directory).pipe(Effect.orElseSucceed(() => undefined)) + if (!resolved) continue + yield* watch(resolved, "directory") + const files = yield* fs + .scan("{*.md,**/SKILL.md}", { cwd: resolved, absolute: true, include: "file", symlink: true, dot: true }) + .pipe(Effect.orElseSucceed(() => [] as string[])) + for (const filepath of files.toSorted()) { + const content = yield* fs.readFileStringSafe(filepath).pipe(Effect.orElseSucceed(() => undefined)) + if (!content) continue + const parsed = SkillFile.parse(resolved, filepath, content) + if (parsed._tag === "Parsed") loaded.set(parsed.skill.id, parsed.skill) + } + } + skills.splice(0, skills.length, ...loaded.values()) + }, + (effect) => lock.withPermit(effect), + ) + + const reload = refresh().pipe(Effect.andThen(ctx.skill.reload())) + const updates = yield* PubSub.subscribe(changes) + yield* Stream.fromSubscription(updates).pipe( + Stream.debounce("100 millis"), + Stream.runForEach(() => reload), + Effect.forkScoped({ startImmediately: true }), + ) + yield* config.changes().pipe( + Stream.runForEach(() => reload), + Effect.forkScoped({ startImmediately: true }), + ) + yield* refresh() + yield* ctx.skill.transform((editor) => { + for (const skill of skills) editor.add(skill) + }) + }), +}) diff --git a/packages/core/src/config/plugin/skill.ts b/packages/core/src/config/plugin/skill.ts index 79cba9c4abf..1338cab3400 100644 --- a/packages/core/src/config/plugin/skill.ts +++ b/packages/core/src/config/plugin/skill.ts @@ -80,13 +80,8 @@ export const Plugin = define({ if (result.some((item) => Skill.Source.equals(item, source))) return result.push(source) } - const claude = loaded.entries.flatMap((entry) => (entry.type === "claude" ? [entry.path] : [])) - const agents = loaded.entries.flatMap((entry) => (entry.type === "agents" ? [entry.path] : [])) const directories = loaded.entries.flatMap((entry) => (entry.type === "directory" ? [entry.path] : [])) const items = loaded.entries.flatMap((entry) => (entry.type === "document" ? (entry.info.skills ?? []) : [])) - for (const directory of [...claude, ...agents]) { - add(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) })) - } for (const directory of directories) { add(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skill")) })) add(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) })) diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index 1691a3c7bbe..bdb0eb23988 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -25,6 +25,7 @@ import { ConfigReferencePlugin } from "../config/plugin/reference.js" import { ConfigShellPlugin } from "../config/plugin/shell.js" import { ConfigSnapshotPlugin } from "../config/plugin/snapshot.js" import { ConfigSkillPlugin } from "../config/plugin/skill.js" +import { ConfigCompatibilityPlugin } from "../config/plugin/compatibility.js" import { ConfigToolOutputPlugin } from "../config/plugin/tool-output.js" import { ConfigWebSearchPlugin } from "../config/plugin/websearch.js" import { ConfigWorktreePlugin } from "../config/plugin/worktree.js" @@ -236,6 +237,7 @@ const post = [ ConfigShellPlugin.Plugin, ConfigSnapshotPlugin.Plugin, ConfigToolOutputPlugin.Plugin, + ConfigCompatibilityPlugin.Plugin, ConfigSkillPlugin.Plugin, ConfigProviderPlugin.Plugin, ConfigWebSearchPlugin.Plugin, diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index b593c19dd6c..09b1de4d4a1 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -4,7 +4,7 @@ import { describe, expect, test } from "bun:test" import { Effect, Fiber, Layer, Logger, Schema, Stream } from "effect" import { FastCheck } from "effect/testing" import { Config } from "@opencode/core/config" -import { AgentsDirectory, Directory, Document, Event, Info } from "@opencode/schema/config" +import { Directory, Document, Event, Info } from "@opencode/schema/config" import { ConfigModel } from "@opencode/schema/config/model" import { ConfigProvider } from "@opencode/schema/config/provider" import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder" @@ -83,8 +83,6 @@ describe("Config", () => { const global = path.join(tmp.path, "global") const home = path.join(global, "home") const project = path.join(home, "project") - const ambient = (entries: readonly { type: string }[]) => - entries.filter((entry) => entry.type === "claude" || entry.type === "agents") return Effect.promise(() => Promise.all([ fs.mkdir(project, { recursive: true }), @@ -96,7 +94,8 @@ describe("Config", () => { Effect.gen(function* () { // The fixture is real: with global enabled the walk finds both. const config = yield* Config.Service - expect(ambient(yield* config.entries()).length).toBe(2) + const compatibility = yield* config.compatibility!() + expect([...compatibility.claude, ...compatibility.agents]).toHaveLength(2) }).pipe(Effect.provide(testLayer(project, global))), ), Effect.andThen( @@ -105,7 +104,7 @@ describe("Config", () => { // project walk enabled. Effect.gen(function* () { const config = yield* Config.Service - expect(ambient(yield* config.entries())).toEqual([]) + expect(yield* config.compatibility!()).toEqual({ claude: [], agents: [] }) const watcher = yield* Watcher.Test expect( (yield* watcher.subscriptions()).filter((watch) => watch.type === "entries" && watch.path === home), @@ -431,7 +430,6 @@ describe("Config", () => { info: new Info({ model: selection("openrouter/openai/gpt-5") }), }), new Directory({ type: "directory", path: AbsolutePath.make("/skills") }), - new AgentsDirectory({ type: "agents", path: AbsolutePath.make("/agents") }), new Document({ type: "document", info: new Info({}) }), new Document({ type: "document", @@ -912,16 +910,12 @@ describe("Config", () => { fs.mkdir(project, { recursive: true }), ]), ) - const entries = yield* Config.Service.use((config) => config.entries()).pipe( + const compatibility = yield* Config.Service.use((config) => config.compatibility!()).pipe( Effect.provide(testLayer(project, global)), ) - expect(entries.filter((entry) => entry.type === "claude").map((entry) => entry.path)).toEqual([ - AbsolutePath.make(path.join(home, ".claude")), - ]) - expect(entries.filter((entry) => entry.type === "agents").map((entry) => entry.path)).toEqual([ - AbsolutePath.make(path.join(home, ".agents")), - ]) + expect(compatibility.claude).toEqual([AbsolutePath.make(path.join(home, ".claude"))]) + expect(compatibility.agents).toEqual([AbsolutePath.make(path.join(home, ".agents"))]) }), ), ), @@ -1560,18 +1554,19 @@ describe("Config", () => { const config = yield* Config.Service const entries = (yield* config.entries()).filter((entry) => !entry.path || inFixture(tmp.path, entry.path)) const documents = entries.filter((entry) => entry.type === "document") + const compatibility = yield* config.compatibility!() expect(entries.filter((entry) => entry.type === "directory").map((entry) => entry.path)).toEqual([ AbsolutePath.make(global), AbsolutePath.make(path.join(root, ".opencode")), AbsolutePath.make(path.join(directory, ".opencode")), ]) - expect(entries.filter((entry) => entry.type === "agents").map((entry) => entry.path)).toEqual([ + expect(compatibility.agents.filter((path) => inFixture(tmp.path, path))).toEqual([ AbsolutePath.make(globalAgents), AbsolutePath.make(path.join(root, ".agents")), AbsolutePath.make(path.join(directory, ".agents")), ]) - expect(entries.filter((entry) => entry.type === "claude").map((entry) => entry.path)).toEqual([ + expect(compatibility.claude.filter((path) => inFixture(tmp.path, path))).toEqual([ AbsolutePath.make(globalClaude), AbsolutePath.make(path.join(root, ".claude")), AbsolutePath.make(path.join(directory, ".claude")), @@ -1586,12 +1581,6 @@ describe("Config", () => { "directory-dot", ]) expect(entries.map((entry) => (entry.type === "document" ? entry.info.$schema : entry.path))).toEqual([ - AbsolutePath.make(globalClaude), - AbsolutePath.make(path.join(root, ".claude")), - AbsolutePath.make(path.join(directory, ".claude")), - AbsolutePath.make(globalAgents), - AbsolutePath.make(path.join(root, ".agents")), - AbsolutePath.make(path.join(directory, ".agents")), "global", AbsolutePath.make(global), "outside", diff --git a/packages/core/test/config/skill.test.ts b/packages/core/test/config/skill.test.ts index 165f4da355d..d226842e946 100644 --- a/packages/core/test/config/skill.test.ts +++ b/packages/core/test/config/skill.test.ts @@ -3,8 +3,9 @@ import path from "path" import { describe, expect, test } from "bun:test" import { Deferred, Effect, Fiber, Layer, Schema, Stream } from "effect" import { Config } from "@opencode/core/config" -import { AgentsDirectory, ClaudeDirectory, Directory, Document, type Entry, Info } from "@opencode/schema/config" +import { Directory, Document, type Entry, Info } from "@opencode/schema/config" import { ConfigSkillPlugin } from "@opencode/core/config/plugin/skill" +import { ConfigCompatibilityPlugin } from "@opencode/core/config/plugin/compatibility" import { SkillFile } from "@opencode/core/config/plugin/skill-file" import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder" import { Watcher } from "@opencode/core/filesystem/watcher" @@ -47,18 +48,26 @@ const startEntries = Effect.fnUntraced(function* ( directory: string, home = directory, discovery = emptyDiscovery, + compatibility: { readonly claude: readonly AbsolutePath[]; readonly agents: readonly AbsolutePath[] } = { + claude: [], + agents: [], + }, ) { const service = yield* Skill.Service + const pluginHost = host({ + skill: { + list: () => Effect.die("unused skill.list"), + transform: service.transform, + reload: service.reload, + }, + }) + yield* ConfigCompatibilityPlugin.Plugin.effect(pluginHost).pipe( + Effect.provide(Config.testLayer(entries, compatibility)), + ) yield* ConfigSkillPlugin.Plugin.effect( - host({ - skill: { - list: () => Effect.die("unused skill.list"), - transform: service.transform, - reload: service.reload, - }, - }), + pluginHost, ).pipe( - Effect.provide(Config.testLayer(entries)), + Effect.provide(Config.testLayer(entries, compatibility)), Effect.provideService(SkillDiscovery.Service, discovery), Effect.provideService(Global.Service, Global.Service.of({ ...Global.make(), home })), Effect.provideService(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))), @@ -82,7 +91,7 @@ const start = (skills: string[], directory: string, discovery = emptyDiscovery) const discover = (directory: string, global: string) => Effect.gen(function* () { const config = yield* Config.Service - return yield* config.entries() + return { entries: yield* config.entries(), compatibility: yield* config.compatibility!() } }).pipe( Effect.provide( AppNodeBuilder.build(LayerNode.group([Config.node, Bus.node]), [ @@ -175,13 +184,13 @@ describe("ConfigSkillPlugin.Plugin", () => { yield* startEntries( [ - new ClaudeDirectory({ type: "claude", path: AbsolutePath.make(claude) }), - new AgentsDirectory({ type: "agents", path: AbsolutePath.make(agents) }), new Directory({ type: "directory", path: AbsolutePath.make(opencode) }), new Document({ type: "document", info: decode({ skills: ["~/shared", "./relative"] }) }), ], directory, home, + emptyDiscovery, + { claude: [AbsolutePath.make(claude)], agents: [AbsolutePath.make(agents)] }, ) const watcher = yield* Watcher.Test expect(yield* watcher.subscriptions()).toEqual(expected.map((item) => ({ path: item, type: "directory" }))) @@ -245,8 +254,14 @@ describe("ConfigSkillPlugin.Plugin", () => { await write(worktreeSkills, "review", "Worktree") }) - const entries = yield* discover(worktree, path.join(tmp.path, "global")) - const skill = yield* startEntries(entries, worktree) + const discovered = yield* discover(worktree, path.join(tmp.path, "global")) + const skill = yield* startEntries( + discovered.entries, + worktree, + worktree, + emptyDiscovery, + discovered.compatibility, + ) const review = (yield* skill.list()).find((item) => item.id === "review") expect(review?.description).toBe("Worktree") diff --git a/packages/protocol/openapi.json b/packages/protocol/openapi.json index 9db168b67a4..aa6e09d0f99 100644 --- a/packages/protocol/openapi.json +++ b/packages/protocol/openapi.json @@ -13048,34 +13048,6 @@ }, "additionalProperties": false }, - "Config.AgentsDirectoryEncoded": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["agents"] - }, - "path": { - "type": "string" - } - }, - "required": ["type", "path"], - "additionalProperties": false - }, - "Config.ClaudeDirectoryEncoded": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["claude"] - }, - "path": { - "type": "string" - } - }, - "required": ["type", "path"], - "additionalProperties": false - }, "Config.CommandEncoded": { "type": "object", "properties": { @@ -13164,12 +13136,6 @@ }, { "$ref": "#/components/schemas/Config.DirectoryEncoded" - }, - { - "$ref": "#/components/schemas/Config.AgentsDirectoryEncoded" - }, - { - "$ref": "#/components/schemas/Config.ClaudeDirectoryEncoded" } ] }, diff --git a/packages/schema/src/config.ts b/packages/schema/src/config.ts index 11474fbe508..222bbef33c2 100644 --- a/packages/schema/src/config.ts +++ b/packages/schema/src/config.ts @@ -132,17 +132,7 @@ export class Directory extends Schema.Class("Config.Directory")({ path: AbsolutePath, }) {} -export class AgentsDirectory extends Schema.Class("Config.AgentsDirectory")({ - type: Schema.Literal("agents"), - path: AbsolutePath, -}) {} - -export class ClaudeDirectory extends Schema.Class("Config.ClaudeDirectory")({ - type: Schema.Literal("claude"), - path: AbsolutePath, -}) {} - -export const Entry = Schema.Union([Document, Directory, AgentsDirectory, ClaudeDirectory]).annotate({ +export const Entry = Schema.Union([Document, Directory]).annotate({ identifier: "Config.Entry", }) export type Entry = typeof Entry.Type diff --git a/packages/schema/test/config.test.ts b/packages/schema/test/config.test.ts index 60852ef5a3d..37c1ecfac0c 100644 --- a/packages/schema/test/config.test.ts +++ b/packages/schema/test/config.test.ts @@ -49,8 +49,6 @@ describe("Config.Entry", () => { }), new Config.Document({ type: "document", info: new Config.Info({ shell: "/bin/zsh" }) }), new Config.Directory({ type: "directory", path: AbsolutePath.make("/project/.opencode") }), - new Config.AgentsDirectory({ type: "agents", path: AbsolutePath.make("/project/.agents") }), - new Config.ClaudeDirectory({ type: "claude", path: AbsolutePath.make("/project/.claude") }), ] const encoded = Schema.encodeSync(Schema.Array(Config.Entry))(entries) @@ -59,7 +57,7 @@ describe("Config.Entry", () => { expect(decoded).toEqual(entries) expect(decoded[0]).toBeInstanceOf(Config.Document) expect(decoded[1]).not.toHaveProperty("path") - expect(decoded.map((entry) => entry.type)).toEqual(["document", "document", "directory", "agents", "claude"]) + expect(decoded.map((entry) => entry.type)).toEqual(["document", "document", "directory"]) expect(decoded[0]?.type === "document" ? decoded[0].info.permissions : undefined).toEqual([ { action: "shell", resource: "*", effect: "ask" }, { action: "shell", resource: "git status", effect: "allow" }, diff --git a/services/www/openapi.json b/services/www/openapi.json index 9db168b67a4..aa6e09d0f99 100644 --- a/services/www/openapi.json +++ b/services/www/openapi.json @@ -13048,34 +13048,6 @@ }, "additionalProperties": false }, - "Config.AgentsDirectoryEncoded": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["agents"] - }, - "path": { - "type": "string" - } - }, - "required": ["type", "path"], - "additionalProperties": false - }, - "Config.ClaudeDirectoryEncoded": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["claude"] - }, - "path": { - "type": "string" - } - }, - "required": ["type", "path"], - "additionalProperties": false - }, "Config.CommandEncoded": { "type": "object", "properties": { @@ -13164,12 +13136,6 @@ }, { "$ref": "#/components/schemas/Config.DirectoryEncoded" - }, - { - "$ref": "#/components/schemas/Config.AgentsDirectoryEncoded" - }, - { - "$ref": "#/components/schemas/Config.ClaudeDirectoryEncoded" } ] }, diff --git a/services/www/public/openapi.json b/services/www/public/openapi.json index 9db168b67a4..aa6e09d0f99 100644 --- a/services/www/public/openapi.json +++ b/services/www/public/openapi.json @@ -13048,34 +13048,6 @@ }, "additionalProperties": false }, - "Config.AgentsDirectoryEncoded": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["agents"] - }, - "path": { - "type": "string" - } - }, - "required": ["type", "path"], - "additionalProperties": false - }, - "Config.ClaudeDirectoryEncoded": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["claude"] - }, - "path": { - "type": "string" - } - }, - "required": ["type", "path"], - "additionalProperties": false - }, "Config.CommandEncoded": { "type": "object", "properties": { @@ -13164,12 +13136,6 @@ }, { "$ref": "#/components/schemas/Config.DirectoryEncoded" - }, - { - "$ref": "#/components/schemas/Config.AgentsDirectoryEncoded" - }, - { - "$ref": "#/components/schemas/Config.ClaudeDirectoryEncoded" } ] },