diff --git a/CHANGELOG.md b/CHANGELOG.md
index c5e83d6e7..55995339e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [4.2.1] - Unreleased
+
+### Fixed
+
+- Team Mode fresh-install diagnostics now log the resolved `team_mode` config and tool-registry team tool count, making #3893-style missing `team_*` registrations visible instead of silent.
+- Added a regression test proving a fresh minimal user config with `{ "team_mode": { "enabled": true } }` registers all 12 `team_*` tools.
+
## [4.2.0] - 2026-05-15
### Added
@@ -37,3 +44,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **First-prompt watchdog supersession history (L16)**: PR #3952 was superseded by PR #4051 (rebased over #4007/factory refactor with `internallyAbortedSessions` threading). The supersession represents conflict resolution, not a feature pivot. The final watchdog logic shipped via #4051 + `a130fa70d` covers subagent first-prompt silence past 90 seconds with cleanup via session.deleted.
[4.2.0]: https://github.com/code-yeongyu/oh-my-openagent/compare/v4.1.2...v4.2.0
+[4.2.1]: https://github.com/code-yeongyu/oh-my-openagent/compare/v4.2.0...HEAD
diff --git a/docs/guide/team-mode.md b/docs/guide/team-mode.md
index be2eaf7e5..e6d00d2bf 100644
--- a/docs/guide/team-mode.md
+++ b/docs/guide/team-mode.md
@@ -29,6 +29,8 @@ Add to user config `~/.config/opencode/oh-my-openagent.jsonc` or project config
After enabling, restart opencode. The 12 `team_*` tools become available.
+> Bug-fix note: v4.2.1 adds a fresh-install regression test for this minimal config and logs the resolved `team_mode` state plus team tool count during startup. If the tools still do not appear after restart, inspect `oh-my-opencode.log` for the loaded config path and `[tool-registry] Built tool registry` entry.
+
## Config schema (11 fields)
All fields live under `team_mode`:
diff --git a/src/plugin-config.ts b/src/plugin-config.ts
index 0914be7b8..b203b00ac 100644
--- a/src/plugin-config.ts
+++ b/src/plugin-config.ts
@@ -179,7 +179,10 @@ export function loadConfigFromPath(
if (result.success) {
addAgentOrderWarnings(configPath, result.data.agent_order);
- log(`Config loaded from ${configPath}`, { agents: result.data.agents });
+ log(`Config loaded from ${configPath}`, {
+ agents: result.data.agents,
+ team_mode: result.data.team_mode,
+ });
return result.data;
}
@@ -195,7 +198,10 @@ export function loadConfigFromPath(
const partialResult = parseConfigPartially(rawConfig);
if (partialResult) {
addAgentOrderWarnings(configPath, partialResult.agent_order);
- log(`Partial config loaded from ${configPath}`, { agents: partialResult.agents });
+ log(`Partial config loaded from ${configPath}`, {
+ agents: partialResult.agents,
+ team_mode: partialResult.team_mode,
+ });
return partialResult;
}
@@ -394,6 +400,7 @@ export function loadPluginConfig(
log("Final merged config", {
agents: config.agents,
+ team_mode: config.team_mode,
disabled_agents: config.disabled_agents,
disabled_mcps: config.disabled_mcps,
disabled_hooks: config.disabled_hooks,
diff --git a/src/plugin/tool-registry.team-mode.test.ts b/src/plugin/tool-registry.team-mode.test.ts
index d858dee45..787c7012a 100644
--- a/src/plugin/tool-registry.team-mode.test.ts
+++ b/src/plugin/tool-registry.team-mode.test.ts
@@ -1,6 +1,9 @@
///
-import { describe, expect, mock, test } from "bun:test"
+import { afterEach, describe, expect, mock, test } from "bun:test"
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
+import { tmpdir } from "node:os"
+import { join } from "node:path"
import { tool } from "@opencode-ai/plugin"
@@ -16,6 +19,20 @@ const fakeTool = tool({
},
})
+const tempDirs: string[] = []
+
+afterEach(() => {
+ delete process.env.OPENCODE_CONFIG_DIR
+
+ for (const tempDir of tempDirs.splice(0)) {
+ rmSync(tempDir, { recursive: true, force: true })
+ }
+})
+
+async function importFreshPluginConfigModule(): Promise {
+ return import(`../plugin-config?team-mode-fresh-install=${Date.now()}-${Math.random()}`)
+}
+
function createPluginConfig() {
return OhMyOpenCodeConfigSchema.parse({
git_master: {
@@ -30,6 +47,83 @@ function createPluginConfig() {
}
describe("team-mode tool registry wiring", () => {
+ test("registers team tools from a fresh-install minimal user config", async () => {
+ // given
+ const rootDir = mkdtempSync(join(tmpdir(), "omo-team-mode-fresh-install-"))
+ tempDirs.push(rootDir)
+ const userConfigDir = join(rootDir, "home", ".config", "opencode")
+ const projectDir = join(rootDir, "project")
+
+ mkdirSync(userConfigDir, { recursive: true })
+ mkdirSync(projectDir, { recursive: true })
+ writeFileSync(
+ join(userConfigDir, "oh-my-openagent.json"),
+ JSON.stringify({ team_mode: { enabled: true } }),
+ )
+ process.env.OPENCODE_CONFIG_DIR = userConfigDir
+
+ const { loadPluginConfig } = await importFreshPluginConfigModule()
+ const pluginConfig = loadPluginConfig(projectDir, {})
+
+ // when
+ const result = createToolRegistry({
+ ctx: { directory: projectDir, client: {} } as Parameters[0]["ctx"],
+ pluginConfig,
+ 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: mock(() => fakeTool),
+ createTeamDeleteTool: mock(() => fakeTool),
+ createTeamShutdownRequestTool: mock(() => fakeTool),
+ createTeamApproveShutdownTool: mock(() => fakeTool),
+ createTeamRejectShutdownTool: mock(() => fakeTool),
+ createTeamSendMessageTool: mock(() => fakeTool),
+ createTeamTaskCreateTool: mock(() => fakeTool),
+ createTeamTaskListTool: mock(() => fakeTool),
+ createTeamTaskUpdateTool: mock(() => fakeTool),
+ createTeamTaskGetTool: mock(() => fakeTool),
+ createTeamStatusTool: mock(() => fakeTool),
+ createTeamListTool: mock(() => fakeTool),
+ },
+ })
+
+ // then
+ expect(pluginConfig.team_mode?.enabled).toBe(true)
+ expect(result.filteredTools).toHaveProperty("team_create")
+ expect(result.filteredTools).toHaveProperty("team_send_message")
+ expect(result.filteredTools).toHaveProperty("team_task_create")
+ expect(result.filteredTools).toHaveProperty("team_status")
+ expect(Object.keys(result.filteredTools).filter((toolName) => toolName.startsWith("team_"))).toHaveLength(12)
+ })
+
test("passes ctx.client into every team tool factory", () => {
// given
const client = {} as OpencodeClient
diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts
index 30c311c2e..a95bc9ab9 100644
--- a/src/plugin/tool-registry.ts
+++ b/src/plugin/tool-registry.ts
@@ -356,6 +356,14 @@ export function createToolRegistry(args: {
...hashlineToolsRecord,
}
+ const allToolNames = Object.keys(allTools)
+ const teamToolCount = allToolNames.filter((toolName) => toolName.startsWith("team_")).length
+ log("[tool-registry] Built tool registry", {
+ totalTools: allToolNames.length,
+ teamModeEnabled: pluginConfig.team_mode?.enabled ?? false,
+ teamToolCount,
+ })
+
for (const toolDefinition of Object.values(allTools)) {
normalizeToolArgSchemas(toolDefinition)
}
diff --git a/src/shared/logger.ts b/src/shared/logger.ts
index 0fbe58c16..c9b8dde65 100644
--- a/src/shared/logger.ts
+++ b/src/shared/logger.ts
@@ -89,6 +89,11 @@ interface LoggerTestOverrides {
/** @internal test-only seam */
export function _setLoggerForTesting(overrides: LoggerTestOverrides): void {
+ buffer = []
+ if (flushTimer) {
+ clearTimeout(flushTimer)
+ flushTimer = null
+ }
if (overrides.filePath !== undefined) logFile = overrides.filePath
if (overrides.maxSizeBytes !== undefined) maxLogFileSizeBytes = overrides.maxSizeBytes
if (overrides.maxBackups !== undefined) maxLogFileBackups = overrides.maxBackups