diff --git a/src/plugin/tool-registry.test.ts b/src/plugin/tool-registry.test.ts
new file mode 100644
index 000000000..b34086213
--- /dev/null
+++ b/src/plugin/tool-registry.test.ts
@@ -0,0 +1,72 @@
+///
+import { describe, expect, test } from "bun:test"
+import { createToolRegistry } from "./tool-registry"
+import type { OhMyOpenCodeConfig } from "../config/schema"
+
+describe("team system tool registration", () => {
+ test("registers team tools when experimental.team_system is true", () => {
+ const pluginConfig = {
+ experimental: { team_system: true },
+ } as unknown as OhMyOpenCodeConfig
+
+ const result = createToolRegistry({
+ ctx: {} as any,
+ pluginConfig,
+ managers: {} as any,
+ skillContext: {} as any,
+ availableCategories: [],
+ })
+
+ expect(Object.keys(result.filteredTools)).toContain("team_create")
+ expect(Object.keys(result.filteredTools)).toContain("team_delete")
+ expect(Object.keys(result.filteredTools)).toContain("send_message")
+ expect(Object.keys(result.filteredTools)).toContain("read_inbox")
+ expect(Object.keys(result.filteredTools)).toContain("read_config")
+ expect(Object.keys(result.filteredTools)).toContain("force_kill_teammate")
+ expect(Object.keys(result.filteredTools)).toContain("process_shutdown_approved")
+ })
+
+ test("does not register team tools when experimental.team_system is false", () => {
+ const pluginConfig = {
+ experimental: { team_system: false },
+ } as unknown as OhMyOpenCodeConfig
+
+ const result = createToolRegistry({
+ ctx: {} as any,
+ pluginConfig,
+ managers: {} as any,
+ skillContext: {} as any,
+ availableCategories: [],
+ })
+
+ expect(Object.keys(result.filteredTools)).not.toContain("team_create")
+ expect(Object.keys(result.filteredTools)).not.toContain("team_delete")
+ expect(Object.keys(result.filteredTools)).not.toContain("send_message")
+ expect(Object.keys(result.filteredTools)).not.toContain("read_inbox")
+ expect(Object.keys(result.filteredTools)).not.toContain("read_config")
+ expect(Object.keys(result.filteredTools)).not.toContain("force_kill_teammate")
+ expect(Object.keys(result.filteredTools)).not.toContain("process_shutdown_approved")
+ })
+
+ test("does not register team tools when experimental.team_system is undefined", () => {
+ const pluginConfig = {
+ experimental: {},
+ } as unknown as OhMyOpenCodeConfig
+
+ const result = createToolRegistry({
+ ctx: {} as any,
+ pluginConfig,
+ managers: {} as any,
+ skillContext: {} as any,
+ availableCategories: [],
+ })
+
+ expect(Object.keys(result.filteredTools)).not.toContain("team_create")
+ expect(Object.keys(result.filteredTools)).not.toContain("team_delete")
+ expect(Object.keys(result.filteredTools)).not.toContain("send_message")
+ expect(Object.keys(result.filteredTools)).not.toContain("read_inbox")
+ expect(Object.keys(result.filteredTools)).not.toContain("read_config")
+ expect(Object.keys(result.filteredTools)).not.toContain("force_kill_teammate")
+ expect(Object.keys(result.filteredTools)).not.toContain("process_shutdown_approved")
+ })
+})
diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts
index 94c30bc6f..0e48b8dba 100644
--- a/src/plugin/tool-registry.ts
+++ b/src/plugin/tool-registry.ts
@@ -118,8 +118,8 @@ export function createToolRegistry(args: {
}
: {}
- const agentTeamsEnabled = pluginConfig.experimental?.agent_teams ?? false
- const agentTeamsRecord: Record = agentTeamsEnabled
+ const teamSystemEnabled = pluginConfig.experimental?.team_system ?? false
+ const agentTeamsRecord: Record = teamSystemEnabled
? createAgentTeamsTools(managers.backgroundManager, {
client: ctx.client,
userCategories: pluginConfig.categories,
diff --git a/src/tools/agent-teams/messaging-tools.test.ts b/src/tools/agent-teams/messaging-tools.test.ts
index 32ee20fc7..08b116e88 100644
--- a/src/tools/agent-teams/messaging-tools.test.ts
+++ b/src/tools/agent-teams/messaging-tools.test.ts
@@ -7,6 +7,7 @@ import { join } from "node:path"
import type { BackgroundManager } from "../../features/background-agent"
import { readInbox } from "./inbox-store"
import { createAgentTeamsTools } from "./tools"
+import { readTeamConfig, upsertTeammate, writeTeamConfig } from "./team-config-store"
interface TestToolContext {
sessionID: string
@@ -63,18 +64,56 @@ function createMockManager(): { manager: BackgroundManager; resumeCalls: ResumeC
}
async function setupTeamWithWorker(
- tools: ReturnType,
+ _tools: ReturnType,
context: TestToolContext,
teamName = "core",
workerName = "worker_1",
): Promise {
- await executeJsonTool(tools, "team_create", { team_name: teamName }, context)
- await executeJsonTool(
- tools,
- "spawn_teammate",
- { team_name: teamName, name: workerName, prompt: "Handle tasks", category: "quick" },
- context,
- )
+ await executeJsonTool(_tools, "team_create", { team_name: teamName }, context)
+
+ const config = readTeamConfig(teamName)
+ if (config) {
+ const teammate = {
+ agentId: `agent-${randomUUID()}`,
+ name: workerName,
+ agentType: "teammate" as const,
+ category: "quick",
+ model: "default",
+ prompt: "Handle tasks",
+ joinedAt: new Date().toISOString(),
+ color: "#FF5733",
+ cwd: process.cwd(),
+ planModeRequired: false,
+ subscriptions: [],
+ backendType: "native" as const,
+ isActive: true,
+ }
+ const updatedConfig = upsertTeammate(config, teammate)
+ writeTeamConfig(teamName, updatedConfig)
+ }
+}
+
+async function addTeammateManually(teamName: string, workerName: string): Promise {
+ const config = readTeamConfig(teamName)
+ if (config) {
+ const teammate = {
+ agentId: `agent-${randomUUID()}`,
+ name: workerName,
+ agentType: "teammate" as const,
+ category: "quick",
+ model: "default",
+ prompt: "Handle tasks",
+ joinedAt: new Date().toISOString(),
+ color: "#FF5733",
+ cwd: process.cwd(),
+ planModeRequired: false,
+ subscriptions: [],
+ backendType: "native" as const,
+ isActive: true,
+ }
+ const updatedConfig = upsertTeammate(config, teammate)
+ writeTeamConfig(teamName, updatedConfig)
+ }
}
describe("agent-teams messaging tools", () => {
@@ -197,12 +236,7 @@ describe("agent-teams messaging tools", () => {
const leadContext = createContext()
await executeJsonTool(tools, "team_create", { team_name: tn }, leadContext)
for (const name of ["worker_1", "worker_2"]) {
- await executeJsonTool(
- tools,
- "spawn_teammate",
- { team_name: tn, name, prompt: "Handle tasks", category: "quick" },
- leadContext,
- )
+ await addTeammateManually(tn, name)
}
//#when
diff --git a/src/tools/agent-teams/teammate-tools.ts b/src/tools/agent-teams/teammate-tools.ts
index 9d3541af4..2c8f157e9 100644
--- a/src/tools/agent-teams/teammate-tools.ts
+++ b/src/tools/agent-teams/teammate-tools.ts
@@ -135,7 +135,7 @@ export function createForceKillTeammateTool(manager: BackgroundManager): ToolDef
description: "Force stop a teammate and clean up ownership state.",
args: {
team_name: tool.schema.string().describe("Team name"),
- agent_name: tool.schema.string().describe("Teammate name"),
+ teammate_name: tool.schema.string().describe("Teammate name"),
},
execute: async (args: Record, context: TeamToolContext): Promise => {
try {
@@ -144,17 +144,17 @@ export function createForceKillTeammateTool(manager: BackgroundManager): ToolDef
if (teamError) {
return JSON.stringify({ error: teamError })
}
- const agentError = validateAgentName(input.agent_name)
+ const agentError = validateAgentName(input.teammate_name)
if (agentError) {
return JSON.stringify({ error: agentError })
}
- const shutdownError = await shutdownTeammateWithCleanup(manager, context, input.team_name, input.agent_name)
+ const shutdownError = await shutdownTeammateWithCleanup(manager, context, input.team_name, input.teammate_name)
if (shutdownError) {
return JSON.stringify({ error: shutdownError })
}
- return JSON.stringify({ success: true, message: `${input.agent_name} stopped` })
+ return JSON.stringify({ success: true, message: `${input.teammate_name} stopped` })
} catch (error) {
return JSON.stringify({ error: error instanceof Error ? error.message : "force_kill_teammate_failed" })
}
@@ -167,7 +167,7 @@ export function createProcessShutdownTool(manager: BackgroundManager): ToolDefin
description: "Finalize an approved shutdown by removing teammate and resetting owned tasks.",
args: {
team_name: tool.schema.string().describe("Team name"),
- agent_name: tool.schema.string().describe("Teammate name"),
+ teammate_name: tool.schema.string().describe("Teammate name"),
},
execute: async (args: Record, context: TeamToolContext): Promise => {
try {
@@ -176,20 +176,20 @@ export function createProcessShutdownTool(manager: BackgroundManager): ToolDefin
if (teamError) {
return JSON.stringify({ error: teamError })
}
- if (input.agent_name === "team-lead") {
+ if (input.teammate_name === "team-lead") {
return JSON.stringify({ error: "cannot_shutdown_team_lead" })
}
- const agentError = validateAgentName(input.agent_name)
+ const agentError = validateAgentName(input.teammate_name)
if (agentError) {
return JSON.stringify({ error: agentError })
}
- const shutdownError = await shutdownTeammateWithCleanup(manager, context, input.team_name, input.agent_name)
+ const shutdownError = await shutdownTeammateWithCleanup(manager, context, input.team_name, input.teammate_name)
if (shutdownError) {
return JSON.stringify({ error: shutdownError })
}
- return JSON.stringify({ success: true, message: `${input.agent_name} removed` })
+ return JSON.stringify({ success: true, message: `${input.teammate_name} removed` })
} catch (error) {
return JSON.stringify({ error: error instanceof Error ? error.message : "process_shutdown_failed" })
}
diff --git a/src/tools/agent-teams/tools.ts b/src/tools/agent-teams/tools.ts
index ff32496fa..28d9fa300 100644
--- a/src/tools/agent-teams/tools.ts
+++ b/src/tools/agent-teams/tools.ts
@@ -4,9 +4,7 @@ import type { PluginInput } from "@opencode-ai/plugin"
import type { CategoriesConfig } from "../../config/schema"
import { createReadInboxTool, createSendMessageTool } from "./messaging-tools"
import { createTeamCreateTool, createTeamDeleteTool, createTeamReadConfigTool } from "./team-lifecycle-tools"
-import { createTeamTaskCreateTool, createTeamTaskGetTool, createTeamTaskListTool } from "./team-task-tools"
-import { createTeamTaskUpdateTool } from "./team-task-update-tool"
-import { createForceKillTeammateTool, createProcessShutdownTool, createSpawnTeammateTool } from "./teammate-tools"
+import { createForceKillTeammateTool, createProcessShutdownApprovedTool } from "./teammate-control-tools"
export interface AgentTeamsToolOptions {
client?: PluginInput["client"]
@@ -15,25 +13,16 @@ export interface AgentTeamsToolOptions {
}
export function createAgentTeamsTools(
- manager: BackgroundManager,
- options?: AgentTeamsToolOptions,
+ _manager: BackgroundManager,
+ _options?: AgentTeamsToolOptions,
): Record {
return {
team_create: createTeamCreateTool(),
team_delete: createTeamDeleteTool(),
- spawn_teammate: createSpawnTeammateTool(manager, {
- client: options?.client,
- userCategories: options?.userCategories,
- sisyphusJuniorModel: options?.sisyphusJuniorModel,
- }),
- send_message: createSendMessageTool(manager),
+ send_message: createSendMessageTool(_manager),
read_inbox: createReadInboxTool(),
read_config: createTeamReadConfigTool(),
- team_task_create: createTeamTaskCreateTool(),
- team_task_update: createTeamTaskUpdateTool(),
- team_task_list: createTeamTaskListTool(),
- team_task_get: createTeamTaskGetTool(),
- force_kill_teammate: createForceKillTeammateTool(manager),
- process_shutdown_approved: createProcessShutdownTool(manager),
+ force_kill_teammate: createForceKillTeammateTool(),
+ process_shutdown_approved: createProcessShutdownApprovedTool(),
}
}
diff --git a/src/tools/agent-teams/types.ts b/src/tools/agent-teams/types.ts
index 5467cc9b0..5a1b7d60c 100644
--- a/src/tools/agent-teams/types.ts
+++ b/src/tools/agent-teams/types.ts
@@ -101,6 +101,8 @@ export const TeamTaskSchema = TaskObjectSchema
export type TeamTask = z.infer
+export type TeamTaskStatus = "pending" | "in_progress" | "completed" | "deleted"
+
// Input schemas for tools
export const TeamCreateInputSchema = z.object({
team_name: z.string().regex(/^[A-Za-z0-9_-]+$/, "Team name must contain only letters, numbers, hyphens, and underscores").max(64),
@@ -183,6 +185,9 @@ export const TeamSpawnInputSchema = z.object({
name: z.string().min(1),
category: z.string().min(1),
prompt: z.string().min(1),
+ subagent_type: z.string().optional(),
+ model: z.string().optional(),
+ plan_mode_required: z.boolean().optional(),
})
export type TeamSpawnInput = z.infer