mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-24 12:15:51 -04:00
8c94e9005f
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com> Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Ben Guthrie <benjee.012@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com> Co-authored-by: Max Anderson <max.a.anderson95@gmail.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: runvip <164729189+runvip@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Julian Coy <julian@ex-machina.co> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
153 lines
5.5 KiB
TypeScript
153 lines
5.5 KiB
TypeScript
import path from "path"
|
|
import { describe, expect } from "bun:test"
|
|
import { Effect, Layer } from "effect"
|
|
import { AgentV2 } from "@opencode-ai/core/agent"
|
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
import { SkillV2 } from "@opencode-ai/core/skill"
|
|
import { SystemContext } from "@opencode-ai/core/system-context"
|
|
import { SkillGuidance } from "@opencode-ai/core/skill/guidance"
|
|
import { it } from "../lib/effect"
|
|
|
|
const build = AgentV2.ID.make("build")
|
|
const effect = SkillV2.Info.make({
|
|
name: "effect",
|
|
description: "Build applications with Effect",
|
|
location: AbsolutePath.make(path.resolve("/skills/effect/SKILL.md")),
|
|
content: "Effect guidance",
|
|
})
|
|
const hidden = SkillV2.Info.make({
|
|
name: "hidden",
|
|
location: AbsolutePath.make(path.resolve("/skills/hidden/SKILL.md")),
|
|
content: "Undescribed guidance",
|
|
})
|
|
const denied = SkillV2.Info.make({
|
|
name: "denied",
|
|
description: "Must not be advertised",
|
|
location: AbsolutePath.make(path.resolve("/skills/denied/SKILL.md")),
|
|
content: "Denied guidance",
|
|
})
|
|
const manual = SkillV2.Info.make({
|
|
name: "manual",
|
|
description: "Load only when explicitly selected",
|
|
autoinvoke: false,
|
|
location: AbsolutePath.make(path.resolve("/skills/manual/SKILL.md")),
|
|
content: "Manual guidance",
|
|
})
|
|
|
|
const layer = (list: () => SkillV2.Info[]) =>
|
|
AppNodeBuilder.build(SkillGuidance.node, [
|
|
[SkillV2.node, Layer.mock(SkillV2.Service, { list: () => Effect.succeed(list()) })],
|
|
])
|
|
|
|
describe("SkillGuidance", () => {
|
|
it.effect("renders described agent skills and reconciles the complete available list", () => {
|
|
const agent = AgentV2.Info.make({
|
|
...AgentV2.Info.empty(build),
|
|
permissions: [{ action: "skill", resource: "denied", effect: "deny" }],
|
|
})
|
|
let skills = [hidden, denied, manual, effect]
|
|
return Effect.gen(function* () {
|
|
const guidance = yield* SkillGuidance.Service
|
|
const initialized = yield* guidance
|
|
.load({ id: agent.id, info: agent })
|
|
.pipe(Effect.flatMap(SystemContext.initialize))
|
|
|
|
expect(initialized.baseline).toBe(
|
|
[
|
|
"Skills provide specialized instructions and workflows for specific tasks.",
|
|
"Use the skill tool to load a skill when a task matches its description.",
|
|
"<available_skills>",
|
|
" <skill>",
|
|
" <name>effect</name>",
|
|
" <description>Build applications with Effect</description>",
|
|
" </skill>",
|
|
"</available_skills>",
|
|
].join("\n"),
|
|
)
|
|
expect(initialized.baseline).not.toContain("manual")
|
|
|
|
skills = []
|
|
expect(
|
|
yield* guidance
|
|
.load({ id: agent.id, info: agent })
|
|
.pipe(Effect.flatMap((context) => SystemContext.reconcile(context, initialized.snapshot))),
|
|
).toMatchObject({
|
|
_tag: "Updated",
|
|
text: expect.stringContaining("No skills are currently available."),
|
|
})
|
|
}).pipe(Effect.provide(layer(() => skills)))
|
|
})
|
|
|
|
it.effect("omits guidance when the selected agent denies all skills", () => {
|
|
const agent = AgentV2.Info.make({
|
|
...AgentV2.Info.empty(build),
|
|
permissions: [{ action: "skill", resource: "*", effect: "deny" }],
|
|
})
|
|
return Effect.gen(function* () {
|
|
const guidance = yield* SkillGuidance.Service
|
|
expect(
|
|
yield* guidance.load({ id: agent.id, info: agent }).pipe(Effect.flatMap(SystemContext.initialize)),
|
|
).toEqual({
|
|
baseline: "",
|
|
snapshot: {},
|
|
})
|
|
}).pipe(Effect.provide(layer(() => [effect])))
|
|
})
|
|
|
|
it.effect("omits guidance when a resource-specific denial follows the global denial", () => {
|
|
const agent = AgentV2.Info.make({
|
|
...AgentV2.Info.empty(build),
|
|
permissions: [
|
|
{ action: "skill", resource: "*", effect: "deny" },
|
|
{ action: "skill", resource: "hidden", effect: "deny" },
|
|
],
|
|
})
|
|
return Effect.gen(function* () {
|
|
const guidance = yield* SkillGuidance.Service
|
|
expect(
|
|
yield* guidance.load({ id: agent.id, info: agent }).pipe(Effect.flatMap(SystemContext.initialize)),
|
|
).toEqual({
|
|
baseline: "",
|
|
snapshot: {},
|
|
})
|
|
}).pipe(Effect.provide(layer(() => [effect])))
|
|
})
|
|
|
|
it.effect("retains specifically allowed skills after a global denial", () => {
|
|
const agent = AgentV2.Info.make({
|
|
...AgentV2.Info.empty(build),
|
|
permissions: [
|
|
{ action: "skill", resource: "*", effect: "deny" },
|
|
{ action: "skill", resource: "effect", effect: "allow" },
|
|
],
|
|
})
|
|
return Effect.gen(function* () {
|
|
const guidance = yield* SkillGuidance.Service
|
|
expect(
|
|
(yield* guidance.load({ id: agent.id, info: agent }).pipe(Effect.flatMap(SystemContext.initialize))).baseline,
|
|
).toContain("<name>effect</name>")
|
|
}).pipe(Effect.provide(layer(() => [effect])))
|
|
})
|
|
|
|
it.effect("omits guidance when a specifically allowed skill is denied again", () => {
|
|
const agent = AgentV2.Info.make({
|
|
...AgentV2.Info.empty(build),
|
|
permissions: [
|
|
{ action: "skill", resource: "*", effect: "deny" },
|
|
{ action: "skill", resource: "effect", effect: "allow" },
|
|
{ action: "skill", resource: "effect", effect: "deny" },
|
|
],
|
|
})
|
|
return Effect.gen(function* () {
|
|
const guidance = yield* SkillGuidance.Service
|
|
expect(
|
|
yield* guidance.load({ id: agent.id, info: agent }).pipe(Effect.flatMap(SystemContext.initialize)),
|
|
).toEqual({
|
|
baseline: "",
|
|
snapshot: {},
|
|
})
|
|
}).pipe(Effect.provide(layer(() => [effect])))
|
|
})
|
|
})
|