diff --git a/src/plugin/tool-registry.team-mode.test.ts b/src/plugin/tool-registry.team-mode.test.ts
new file mode 100644
index 000000000..d858dee45
--- /dev/null
+++ b/src/plugin/tool-registry.team-mode.test.ts
@@ -0,0 +1,113 @@
+///
+
+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 {
+ 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[0]["ctx"],
+ pluginConfig: createPluginConfig(),
+ managers: {
+ backgroundManager: {},
+ tmuxSessionManager: {},
+ skillMcpManager: {},
+ } as Parameters[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)
+ })
+})
diff --git a/src/plugin/tool-registry.test.ts b/src/plugin/tool-registry.test.ts
index 5c0a42bfb..7fc2f1723 100644
--- a/src/plugin/tool-registry.test.ts
+++ b/src/plugin/tool-registry.test.ts
@@ -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()
let dispatchOpenClawEvent: ReturnType
+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[0]["toolFactories"]> = {
@@ -52,17 +67,33 @@ const toolFactories: NonNullable[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 {
- return {
+type PluginConfigOverrides = Omit, "team_mode"> & {
+ team_mode?: Partial>
+}
+
+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[0]["ctx"],
+ pluginConfig: createPluginConfig({
+ team_mode: {
+ enabled: true,
+ },
+ }),
+ managers: {
+ backgroundManager: {},
+ tmuxSessionManager: {},
+ skillMcpManager: {},
+ } as Parameters[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[0]["ctx"],
+ pluginConfig: createPluginConfig({
+ team_mode: {
+ enabled: false,
+ },
+ }),
+ managers: {
+ backgroundManager: {},
+ tmuxSessionManager: {},
+ skillMcpManager: {},
+ } as Parameters[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
diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts
index a3e46185a..582c12be5 100644
--- a/src/plugin/tool-registry.ts
+++ b/src/plugin/tool-registry.ts
@@ -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 = 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 = {
...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,
}