test(delegate-task): isolate browser provider coverage
This commit is contained in:
@@ -0,0 +1,163 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
|
||||||
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||||
|
import { tmpdir } from "node:os"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import { clearSkillCache } from "../../features/opencode-skill-loader/skill-content"
|
||||||
|
|
||||||
|
declare const require: NodeJS.Require
|
||||||
|
|
||||||
|
const { afterEach, beforeEach, describe, expect, test } = require("bun:test")
|
||||||
|
|
||||||
|
const SYSTEM_DEFAULT_MODEL = "anthropic/claude-sonnet-4-6"
|
||||||
|
const TOOL_CONTEXT = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnvSnapshot = Record<"CLAUDE_CONFIG_DIR" | "OPENCODE_CONFIG_DIR", string | undefined>
|
||||||
|
type PromptBody = { system?: string }
|
||||||
|
type PromptInput = { body?: PromptBody }
|
||||||
|
|
||||||
|
let envSnapshot: EnvSnapshot
|
||||||
|
let testConfigDir: string | undefined
|
||||||
|
|
||||||
|
function restoreEnvValue(key: keyof EnvSnapshot): void {
|
||||||
|
const value = envSnapshot[key]
|
||||||
|
if (value === undefined) {
|
||||||
|
delete process.env[key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
process.env[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgentBrowserSkillFixture(content: string): void {
|
||||||
|
const unique = `delegate-agent-browser-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||||
|
testConfigDir = join(tmpdir(), unique)
|
||||||
|
const skillDir = join(testConfigDir, "skills", "compound-engineering", "agent-browser")
|
||||||
|
mkdirSync(skillDir, { recursive: true })
|
||||||
|
writeFileSync(
|
||||||
|
join(skillDir, "SKILL.md"),
|
||||||
|
`---\nname: agent-browser\ndescription: Test-owned browser automation skill\n---\n${content}`,
|
||||||
|
)
|
||||||
|
process.env.CLAUDE_CONFIG_DIR = testConfigDir
|
||||||
|
process.env.OPENCODE_CONFIG_DIR = testConfigDir
|
||||||
|
clearSkillCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("delegate task browserProvider propagation", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
envSnapshot = {
|
||||||
|
CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR,
|
||||||
|
OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
|
||||||
|
}
|
||||||
|
testConfigDir = undefined
|
||||||
|
clearSkillCache()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
restoreEnvValue("CLAUDE_CONFIG_DIR")
|
||||||
|
restoreEnvValue("OPENCODE_CONFIG_DIR")
|
||||||
|
clearSkillCache()
|
||||||
|
if (testConfigDir !== undefined) {
|
||||||
|
rmSync(testConfigDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("resolves agent-browser builtin skill when browserProvider is passed", async () => {
|
||||||
|
// given
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
let promptBody: PromptBody | undefined
|
||||||
|
const promptMock = async (input: PromptInput) => {
|
||||||
|
promptBody = input.body
|
||||||
|
return { data: {} }
|
||||||
|
}
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async () => ({ data: { id: "ses_browser_provider" } }),
|
||||||
|
prompt: promptMock,
|
||||||
|
promptAsync: promptMock,
|
||||||
|
messages: async () => ({
|
||||||
|
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }],
|
||||||
|
}),
|
||||||
|
status: async () => ({ data: {} }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: { launch: async () => ({}) },
|
||||||
|
client: mockClient,
|
||||||
|
browserProvider: "agent-browser",
|
||||||
|
})
|
||||||
|
|
||||||
|
// when
|
||||||
|
await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Test browserProvider propagation",
|
||||||
|
prompt: "Do something",
|
||||||
|
category: "ultrabrain",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: ["agent-browser"],
|
||||||
|
},
|
||||||
|
TOOL_CONTEXT,
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
const system = promptBody?.system ?? ""
|
||||||
|
expect(system).toContain("agent-browser")
|
||||||
|
expect(system).toContain("<Category_Context>")
|
||||||
|
expect(system.startsWith("<Category_Context>")).toBe(false)
|
||||||
|
}, { timeout: 20000 })
|
||||||
|
|
||||||
|
test("resolves discovered agent-browser skill by short name without browserProvider", async () => {
|
||||||
|
// given
|
||||||
|
const fixtureContent = "Test-owned agent-browser fixture content"
|
||||||
|
createAgentBrowserSkillFixture(fixtureContent)
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
let promptBody: PromptBody | undefined
|
||||||
|
const promptMock = async (input: PromptInput) => {
|
||||||
|
promptBody = input.body
|
||||||
|
return { data: {} }
|
||||||
|
}
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async () => ({ data: { id: "ses_no_browser_provider" } }),
|
||||||
|
prompt: promptMock,
|
||||||
|
promptAsync: promptMock,
|
||||||
|
messages: async () => ({
|
||||||
|
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }],
|
||||||
|
}),
|
||||||
|
status: async () => ({ data: {} }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: { launch: async () => ({}) },
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Test missing browserProvider",
|
||||||
|
prompt: "Do something",
|
||||||
|
category: "ultrabrain",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: ["agent-browser"],
|
||||||
|
},
|
||||||
|
TOOL_CONTEXT,
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result).toContain("Task completed")
|
||||||
|
expect(result).toContain("ses_no_browser_provider")
|
||||||
|
expect(promptBody?.system).toContain(fixtureContent)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -696,7 +696,7 @@ describe("sisyphus-task", () => {
|
|||||||
//#given - manager.launch returns before sessionID is available
|
//#given - manager.launch returns before sessionID is available
|
||||||
const { createDelegateTask } = require("./tools")
|
const { createDelegateTask } = require("./tools")
|
||||||
|
|
||||||
const tasks = new Map<string, { id: string; sessionID?: string; status: string; description: string; agent: string }>()
|
const tasks = new Map<string, { id: string; sessionId?: string; status: string; description: string; agent: string }>()
|
||||||
const mockManager = {
|
const mockManager = {
|
||||||
getTask: (id: string) => tasks.get(id),
|
getTask: (id: string) => tasks.get(id),
|
||||||
launch: async () => {
|
launch: async () => {
|
||||||
@@ -3222,119 +3222,6 @@ describe("sisyphus-task", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("browserProvider propagation", () => {
|
|
||||||
test("should resolve agent-browser skill when browserProvider is passed", async () => {
|
|
||||||
// given - task configured with browserProvider: "agent-browser"
|
|
||||||
const { createDelegateTask } = require("./tools")
|
|
||||||
let promptBody: any
|
|
||||||
|
|
||||||
const mockManager = { launch: async () => ({}) }
|
|
||||||
|
|
||||||
const promptMock = async (input: any) => {
|
|
||||||
promptBody = input.body
|
|
||||||
return { data: {} }
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockClient = {
|
|
||||||
app: { agents: async () => ({ data: [] }) },
|
|
||||||
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
|
||||||
session: {
|
|
||||||
get: async () => ({ data: { directory: "/project" } }),
|
|
||||||
create: async () => ({ data: { id: "ses_browser_provider" } }),
|
|
||||||
prompt: promptMock,
|
|
||||||
promptAsync: promptMock,
|
|
||||||
messages: async () => ({
|
|
||||||
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }]
|
|
||||||
}),
|
|
||||||
status: async () => ({ data: {} }),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass browserProvider to createDelegateTask
|
|
||||||
const tool = createDelegateTask({
|
|
||||||
manager: mockManager,
|
|
||||||
client: mockClient,
|
|
||||||
browserProvider: "agent-browser",
|
|
||||||
})
|
|
||||||
|
|
||||||
const toolContext = {
|
|
||||||
sessionID: "parent-session",
|
|
||||||
messageID: "parent-message",
|
|
||||||
agent: "sisyphus",
|
|
||||||
abort: new AbortController().signal,
|
|
||||||
}
|
|
||||||
|
|
||||||
// when - request agent-browser skill
|
|
||||||
await tool.execute(
|
|
||||||
{
|
|
||||||
description: "Test browserProvider propagation",
|
|
||||||
prompt: "Do something",
|
|
||||||
category: "ultrabrain",
|
|
||||||
run_in_background: false,
|
|
||||||
load_skills: ["agent-browser"],
|
|
||||||
},
|
|
||||||
toolContext
|
|
||||||
)
|
|
||||||
|
|
||||||
// then - agent-browser skill should be resolved
|
|
||||||
expect(promptBody).toBeDefined()
|
|
||||||
expect(promptBody.system).toBeDefined()
|
|
||||||
expect(promptBody.system).toContain("<Category_Context>")
|
|
||||||
expect(String(promptBody.system).startsWith("<Category_Context>")).toBe(false)
|
|
||||||
}, { timeout: 20000 })
|
|
||||||
|
|
||||||
test("should resolve agent-browser skill even when browserProvider is not set", async () => {
|
|
||||||
// given - delegate_task without browserProvider
|
|
||||||
const { createDelegateTask } = require("./tools")
|
|
||||||
const mockManager = { launch: async () => ({}) }
|
|
||||||
const mockClient = {
|
|
||||||
app: { agents: async () => ({ data: [] }) },
|
|
||||||
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
|
||||||
session: {
|
|
||||||
get: async () => ({ data: { directory: "/project" } }),
|
|
||||||
create: async () => ({ data: { id: "ses_no_browser_provider" } }),
|
|
||||||
prompt: async () => {
|
|
||||||
return { data: {} }
|
|
||||||
},
|
|
||||||
promptAsync: async () => ({ data: {} }),
|
|
||||||
messages: async () => ({
|
|
||||||
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Done" }] }]
|
|
||||||
}),
|
|
||||||
status: async () => ({ data: {} }),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// No browserProvider passed
|
|
||||||
const tool = createDelegateTask({
|
|
||||||
manager: mockManager,
|
|
||||||
client: mockClient,
|
|
||||||
})
|
|
||||||
|
|
||||||
const toolContext = {
|
|
||||||
sessionID: "parent-session",
|
|
||||||
messageID: "parent-message",
|
|
||||||
agent: "sisyphus",
|
|
||||||
abort: new AbortController().signal,
|
|
||||||
}
|
|
||||||
|
|
||||||
// when - request agent-browser skill without browserProvider
|
|
||||||
const result = await tool.execute(
|
|
||||||
{
|
|
||||||
description: "Test missing browserProvider",
|
|
||||||
prompt: "Do something",
|
|
||||||
category: "ultrabrain",
|
|
||||||
run_in_background: false,
|
|
||||||
load_skills: ["agent-browser"],
|
|
||||||
},
|
|
||||||
toolContext
|
|
||||||
)
|
|
||||||
|
|
||||||
// then - the external compound-engineering/agent-browser skill can resolve by unique short name
|
|
||||||
expect(result).toContain("Task completed")
|
|
||||||
expect(result).toContain("ses_no_browser_provider")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("delegate task with short skill name", () => {
|
describe("delegate task with short skill name", () => {
|
||||||
let envCleanup: Record<string, string | undefined>
|
let envCleanup: Record<string, string | undefined>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user