feat(plugin): register team-mode tools and expand registry tests

This commit is contained in:
YeonGyu-Kim
2026-04-28 10:47:33 +09:00
parent 2258c009e9
commit 47e0fbad49
3 changed files with 288 additions and 6 deletions
+113
View File
@@ -0,0 +1,113 @@
/// <reference types="bun-types" />
import { describe, expect, mock, test } from "bun:test"
import { tool } from "@opencode-ai/plugin"
import { OhMyOpenCodeConfigSchema } from "../config"
import type { OpencodeClient } from "../tools/delegate-task/types"
import { createToolRegistry } from "./tool-registry"
const fakeTool = tool({
description: "test tool",
args: {},
async execute(): Promise<string> {
return "ok"
},
})
function createPluginConfig() {
return OhMyOpenCodeConfigSchema.parse({
git_master: {
commit_footer: false,
include_co_authored_by: false,
git_env_prefix: "",
},
team_mode: {
enabled: true,
},
})
}
describe("team-mode tool registry wiring", () => {
test("passes ctx.client into every team tool factory", () => {
// given
const client = {} as OpencodeClient
const createTeamCreateTool = mock(() => fakeTool)
const createTeamDeleteTool = mock(() => fakeTool)
const createTeamShutdownRequestTool = mock(() => fakeTool)
const createTeamApproveShutdownTool = mock(() => fakeTool)
const createTeamRejectShutdownTool = mock(() => fakeTool)
const createTeamSendMessageTool = mock(() => fakeTool)
const createTeamTaskCreateTool = mock(() => fakeTool)
const createTeamTaskListTool = mock(() => fakeTool)
const createTeamTaskUpdateTool = mock(() => fakeTool)
const createTeamTaskGetTool = mock(() => fakeTool)
const createTeamStatusTool = mock(() => fakeTool)
const createTeamListTool = mock(() => fakeTool)
// when
createToolRegistry({
ctx: { directory: "/tmp/team-mode", client } as Parameters<typeof createToolRegistry>[0]["ctx"],
pluginConfig: createPluginConfig(),
managers: {
backgroundManager: {},
tmuxSessionManager: {},
skillMcpManager: {},
} as Parameters<typeof createToolRegistry>[0]["managers"],
skillContext: {
mergedSkills: [],
availableSkills: [],
browserProvider: "playwright",
disabledSkills: new Set(),
},
availableCategories: [],
toolFactories: {
builtinTools: { bash: fakeTool, read: fakeTool },
createBackgroundTools: mock(() => ({})),
createCallOmoAgent: mock(() => fakeTool),
createLookAt: mock(() => fakeTool),
createSkillMcpTool: mock(() => fakeTool),
createSkillTool: mock(() => fakeTool),
createGrepTools: mock(() => ({})),
createGlobTools: mock(() => ({})),
createAstGrepTools: mock(() => ({})),
createSessionManagerTools: mock(() => ({})),
createDelegateTask: mock(() => fakeTool),
discoverCommandsSync: mock(() => []),
interactive_bash: fakeTool,
createTaskCreateTool: mock(() => fakeTool),
createTaskGetTool: mock(() => fakeTool),
createTaskList: mock(() => fakeTool),
createTaskUpdateTool: mock(() => fakeTool),
createHashlineEditTool: mock(() => fakeTool),
createTeamCreateTool,
createTeamDeleteTool,
createTeamShutdownRequestTool,
createTeamApproveShutdownTool,
createTeamRejectShutdownTool,
createTeamSendMessageTool,
createTeamTaskCreateTool,
createTeamTaskListTool,
createTeamTaskUpdateTool,
createTeamTaskGetTool,
createTeamStatusTool,
createTeamListTool,
},
})
// then
expect(createTeamCreateTool).toHaveBeenCalledWith(expect.anything(), client, expect.anything(), expect.anything(), expect.anything())
expect(createTeamDeleteTool).toHaveBeenCalledWith(expect.anything(), client, expect.anything(), expect.anything())
expect(createTeamShutdownRequestTool).toHaveBeenCalledWith(expect.anything(), client)
expect(createTeamApproveShutdownTool).toHaveBeenCalledWith(expect.anything(), client)
expect(createTeamRejectShutdownTool).toHaveBeenCalledWith(expect.anything(), client)
expect(createTeamSendMessageTool).toHaveBeenCalledWith(expect.anything(), client)
expect(createTeamTaskCreateTool).toHaveBeenCalledWith(expect.anything(), client)
expect(createTeamTaskListTool).toHaveBeenCalledWith(expect.anything(), client)
expect(createTeamTaskUpdateTool).toHaveBeenCalledWith(expect.anything(), client)
expect(createTeamTaskGetTool).toHaveBeenCalledWith(expect.anything(), client)
expect(createTeamStatusTool).toHaveBeenCalledWith(expect.anything(), client, expect.anything())
expect(createTeamListTool).toHaveBeenCalledWith(expect.anything(), client)
})
})
+98 -5
View File
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test"
const { beforeEach, describe, expect, mock, spyOn, test } = require("bun:test")
import { tool } from "@opencode-ai/plugin"
import type { OhMyOpenCodeConfig } from "../config"
import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "../config"
import * as openclawRuntimeDispatch from "../openclaw/runtime-dispatch"
import type { ToolsRecord } from "./types"
@@ -28,6 +28,21 @@ const syncSessionCreatedCallbacks: Array<
const trackedPaneBySession = new Map<string, string>()
let dispatchOpenClawEvent: ReturnType<typeof spyOn>
const TEAM_TOOL_NAMES = [
"team_create",
"team_delete",
"team_shutdown_request",
"team_approve_shutdown",
"team_reject_shutdown",
"team_send_message",
"team_task_create",
"team_task_list",
"team_task_update",
"team_task_get",
"team_status",
"team_list",
] as const
const { createToolRegistry, trimToolsToCap } = await import("./tool-registry")
const toolFactories: NonNullable<Parameters<typeof createToolRegistry>[0]["toolFactories"]> = {
@@ -52,17 +67,33 @@ const toolFactories: NonNullable<Parameters<typeof createToolRegistry>[0]["toolF
createTaskList: mock(() => fakeTool),
createTaskUpdateTool: mock(() => fakeTool),
createHashlineEditTool: mock(() => fakeTool),
createTeamApproveShutdownTool: mock(() => fakeTool),
createTeamCreateTool: mock(() => fakeTool),
createTeamDeleteTool: mock(() => fakeTool),
createTeamRejectShutdownTool: mock(() => fakeTool),
createTeamShutdownRequestTool: mock(() => fakeTool),
createTeamSendMessageTool: mock(() => fakeTool),
createTeamTaskCreateTool: mock(() => fakeTool),
createTeamTaskGetTool: mock(() => fakeTool),
createTeamTaskListTool: mock(() => fakeTool),
createTeamTaskUpdateTool: mock(() => fakeTool),
createTeamStatusTool: mock(() => fakeTool),
createTeamListTool: mock(() => fakeTool),
}
function createPluginConfig(overrides: Partial<OhMyOpenCodeConfig> = {}): OhMyOpenCodeConfig {
return {
type PluginConfigOverrides = Omit<Partial<OhMyOpenCodeConfig>, "team_mode"> & {
team_mode?: Partial<NonNullable<OhMyOpenCodeConfig["team_mode"]>>
}
function createPluginConfig(overrides: PluginConfigOverrides = {}): OhMyOpenCodeConfig {
return OhMyOpenCodeConfigSchema.parse({
git_master: {
commit_footer: false,
include_co_authored_by: false,
git_env_prefix: "",
},
...overrides,
}
})
}
beforeEach(() => {
@@ -146,6 +177,68 @@ describe("#given task_system configuration", () => {
})
})
describe("#given team_mode configuration", () => {
test("#when team_mode is enabled #then all 12 team tools are registered", () => {
syncSessionCreatedCallbacks.length = 0
const result = createToolRegistry({
ctx: { directory: "/tmp" } as Parameters<typeof createToolRegistry>[0]["ctx"],
pluginConfig: createPluginConfig({
team_mode: {
enabled: true,
},
}),
managers: {
backgroundManager: {},
tmuxSessionManager: {},
skillMcpManager: {},
} as Parameters<typeof createToolRegistry>[0]["managers"],
skillContext: {
mergedSkills: [],
availableSkills: [],
browserProvider: "playwright",
disabledSkills: new Set(),
},
availableCategories: [],
toolFactories,
})
for (const teamToolName of TEAM_TOOL_NAMES) {
expect(result.filteredTools).toHaveProperty(teamToolName)
}
})
test("#when team_mode is disabled #then zero team tools are registered", () => {
syncSessionCreatedCallbacks.length = 0
const result = createToolRegistry({
ctx: { directory: "/tmp" } as Parameters<typeof createToolRegistry>[0]["ctx"],
pluginConfig: createPluginConfig({
team_mode: {
enabled: false,
},
}),
managers: {
backgroundManager: {},
tmuxSessionManager: {},
skillMcpManager: {},
} as Parameters<typeof createToolRegistry>[0]["managers"],
skillContext: {
mergedSkills: [],
availableSkills: [],
browserProvider: "playwright",
disabledSkills: new Set(),
},
availableCategories: [],
toolFactories,
})
const registeredTeamToolNames = Object.keys(result.filteredTools).filter((toolName) => toolName.startsWith("team_"))
expect(registeredTeamToolNames).toHaveLength(0)
})
})
describe("#given tmux integration is disabled", () => {
test("#when system tmux is available #then interactive_bash remains registered", () => {
syncSessionCreatedCallbacks.length = 0
+77 -1
View File
@@ -6,6 +6,21 @@ import type {
} from "../agents/dynamic-agent-prompt-builder"
import type { OhMyOpenCodeConfig } from "../config"
import { isInteractiveBashEnabled } from "../create-runtime-tmux-config"
import {
createTeamApproveShutdownTool,
createTeamCreateTool,
createTeamDeleteTool,
createTeamRejectShutdownTool,
createTeamShutdownRequestTool,
} from "../features/team-mode/tools/lifecycle"
import { createTeamSendMessageTool } from "../features/team-mode/tools/messaging"
import { createTeamListTool, createTeamStatusTool } from "../features/team-mode/tools/query"
import {
createTeamTaskCreateTool,
createTeamTaskGetTool,
createTeamTaskListTool,
createTeamTaskUpdateTool,
} from "../features/team-mode/tools/tasks"
import * as openclawRuntimeDispatch from "../openclaw/runtime-dispatch"
import type { PluginContext, ToolsRecord } from "./types"
@@ -56,6 +71,18 @@ type ToolRegistryFactories = {
createTaskList: typeof createTaskList
createTaskUpdateTool: typeof createTaskUpdateTool
createHashlineEditTool: typeof createHashlineEditTool
createTeamApproveShutdownTool: typeof createTeamApproveShutdownTool
createTeamCreateTool: typeof createTeamCreateTool
createTeamDeleteTool: typeof createTeamDeleteTool
createTeamRejectShutdownTool: typeof createTeamRejectShutdownTool
createTeamShutdownRequestTool: typeof createTeamShutdownRequestTool
createTeamSendMessageTool: typeof createTeamSendMessageTool
createTeamTaskCreateTool: typeof createTeamTaskCreateTool
createTeamTaskGetTool: typeof createTeamTaskGetTool
createTeamTaskListTool: typeof createTeamTaskListTool
createTeamTaskUpdateTool: typeof createTeamTaskUpdateTool
createTeamStatusTool: typeof createTeamStatusTool
createTeamListTool: typeof createTeamListTool
}
const defaultToolRegistryFactories: ToolRegistryFactories = {
@@ -77,6 +104,18 @@ const defaultToolRegistryFactories: ToolRegistryFactories = {
createTaskList,
createTaskUpdateTool,
createHashlineEditTool,
createTeamApproveShutdownTool,
createTeamCreateTool,
createTeamDeleteTool,
createTeamRejectShutdownTool,
createTeamShutdownRequestTool,
createTeamSendMessageTool,
createTeamTaskCreateTool,
createTeamTaskGetTool,
createTeamTaskListTool,
createTeamTaskUpdateTool,
createTeamStatusTool,
createTeamListTool,
}
export type ToolRegistryResult = {
@@ -178,6 +217,8 @@ export function createToolRegistry(args: {
)
const lookAt = isMultimodalLookerEnabled ? factories.createLookAt(ctx) : null
const getSisyphusJuniorModelOverride = (agentOverride?: { model?: string }): string | undefined => agentOverride?.model
const delegateTask = factories.createDelegateTask({
manager: managers.backgroundManager,
client: ctx.client,
@@ -185,9 +226,10 @@ export function createToolRegistry(args: {
userCategories: pluginConfig.categories,
agentOverrides: pluginConfig.agents,
gitMasterConfig: pluginConfig.git_master,
sisyphusJuniorModel: pluginConfig.agents?.["sisyphus-junior"]?.model,
sisyphusJuniorModel: getSisyphusJuniorModelOverride(pluginConfig.agents?.["sisyphus-junior"]),
browserProvider: skillContext.browserProvider,
disabledSkills: skillContext.disabledSkills,
teamModeEnabled: pluginConfig.team_mode?.enabled ?? false,
availableCategories,
availableSkills: skillContext.availableSkills,
sisyphusAgentConfig: pluginConfig.sisyphus_agent,
@@ -243,6 +285,7 @@ export function createToolRegistry(args: {
getSessionID: getSessionIDForMcp,
gitMasterConfig: pluginConfig.git_master,
browserProvider: skillContext.browserProvider,
teamModeEnabled: pluginConfig.team_mode?.enabled ?? false,
nativeSkills: "skills" in ctx ? (ctx as { skills: SkillLoadOptions["nativeSkills"] }).skills : undefined,
})
@@ -261,6 +304,38 @@ export function createToolRegistry(args: {
? { edit: factories.createHashlineEditTool(ctx) }
: {}
const teamModeToolsRecord: Record<string, ToolDefinition> = pluginConfig.team_mode?.enabled
? {
team_create: factories.createTeamCreateTool(
pluginConfig.team_mode,
ctx.client,
managers.backgroundManager,
managers.tmuxSessionManager,
{
userCategories: pluginConfig.categories,
sisyphusJuniorModel: getSisyphusJuniorModelOverride(pluginConfig.agents?.["sisyphus-junior"]),
agentOverrides: pluginConfig.agents,
},
),
team_delete: factories.createTeamDeleteTool(
pluginConfig.team_mode,
ctx.client,
managers.backgroundManager,
managers.tmuxSessionManager,
),
team_shutdown_request: factories.createTeamShutdownRequestTool(pluginConfig.team_mode, ctx.client),
team_approve_shutdown: factories.createTeamApproveShutdownTool(pluginConfig.team_mode, ctx.client),
team_reject_shutdown: factories.createTeamRejectShutdownTool(pluginConfig.team_mode, ctx.client),
team_send_message: factories.createTeamSendMessageTool(pluginConfig.team_mode, ctx.client),
team_task_create: factories.createTeamTaskCreateTool(pluginConfig.team_mode, ctx.client),
team_task_list: factories.createTeamTaskListTool(pluginConfig.team_mode, ctx.client),
team_task_update: factories.createTeamTaskUpdateTool(pluginConfig.team_mode, ctx.client),
team_task_get: factories.createTeamTaskGetTool(pluginConfig.team_mode, ctx.client),
team_status: factories.createTeamStatusTool(pluginConfig.team_mode, ctx.client, managers.backgroundManager),
team_list: factories.createTeamListTool(pluginConfig.team_mode, ctx.client),
}
: {}
const allTools: Record<string, ToolDefinition> = {
...factories.builtinTools,
...factories.createGrepTools(ctx),
@@ -274,6 +349,7 @@ export function createToolRegistry(args: {
skill_mcp: skillMcpTool,
skill: skillTool,
...(interactiveBashEnabled ? { interactive_bash: factories.interactive_bash } : {}),
...teamModeToolsRecord,
...taskToolsRecord,
...hashlineToolsRecord,
}