Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/config/schema/agent-overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const AgentOverridesSchema = z.object({
explore: AgentOverrideConfigSchema.optional(),
"multimodal-looker": AgentOverrideConfigSchema.optional(),
atlas: AgentOverrideConfigSchema.optional(),
})
}).catchall(AgentOverrideConfigSchema)

export type AgentOverrideConfig = z.infer<typeof AgentOverrideConfigSchema>
export type AgentOverrides = z.infer<typeof AgentOverridesSchema>
16 changes: 3 additions & 13 deletions src/features/background-agent/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
ResumeInput,
} from "./types"
import { TaskHistory } from "./task-history"
import { log, getAgentToolRestrictions, normalizeSDKResponse, promptWithModelSuggestionRetry } from "../../shared"
import { log, buildSubagentTools, normalizeSDKResponse, promptWithModelSuggestionRetry } from "../../shared"
import { setSessionTools } from "../../shared/session-tools-store"
import { ConcurrencyManager } from "./concurrency"
import type { BackgroundTaskConfig, TmuxConfig } from "../../config/schema"
Expand Down Expand Up @@ -335,12 +335,7 @@ export class BackgroundManager {
...(launchVariant ? { variant: launchVariant } : {}),
system: input.skillContent,
tools: (() => {
const tools = {
...getAgentToolRestrictions(input.agent),
task: false,
call_omo_agent: true,
question: false,
}
const tools = buildSubagentTools(input.agent)
setSessionTools(sessionID, tools)
return tools
})(),
Expand Down Expand Up @@ -608,12 +603,7 @@ export class BackgroundManager {
...(resumeModel ? { model: resumeModel } : {}),
...(resumeVariant ? { variant: resumeVariant } : {}),
tools: (() => {
const tools = {
...getAgentToolRestrictions(existingTask.agent),
task: false,
call_omo_agent: true,
question: false,
}
const tools = buildSubagentTools(existingTask.agent)
setSessionTools(existingTask.sessionID!, tools)
return tools
})(),
Expand Down
16 changes: 3 additions & 13 deletions src/features/background-agent/spawner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BackgroundTask, LaunchInput, ResumeInput } from "./types"
import type { OpencodeClient, OnSubagentSessionCreated, QueueItem } from "./constants"
import { TMUX_CALLBACK_DELAY_MS } from "./constants"
import { log, getAgentToolRestrictions, promptWithModelSuggestionRetry } from "../../shared"
import { log, buildSubagentTools, promptWithModelSuggestionRetry } from "../../shared"
import { subagentSessions } from "../claude-code-session-state"
import { getTaskToastManager } from "../task-toast-manager"
import { isInsideTmux } from "../../shared/tmux"
Expand Down Expand Up @@ -140,12 +140,7 @@ export async function startTask(
...(launchModel ? { model: launchModel } : {}),
...(launchVariant ? { variant: launchVariant } : {}),
system: input.skillContent,
tools: {
...getAgentToolRestrictions(input.agent),
task: false,
call_omo_agent: true,
question: false,
},
tools: buildSubagentTools(input.agent),
parts: [{ type: "text", text: input.prompt }],
},
}).catch((error) => {
Expand Down Expand Up @@ -224,12 +219,7 @@ export async function resumeTask(
agent: task.agent,
...(resumeModel ? { model: resumeModel } : {}),
...(resumeVariant ? { variant: resumeVariant } : {}),
tools: {
...getAgentToolRestrictions(task.agent),
task: false,
call_omo_agent: true,
question: false,
},
tools: buildSubagentTools(task.agent),
parts: [{ type: "text", text: input.prompt }],
},
}).catch((error) => {
Expand Down
9 changes: 2 additions & 7 deletions src/features/background-agent/spawner/task-resumer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BackgroundTask, ResumeInput } from "../types"
import { log, getAgentToolRestrictions } from "../../../shared"
import { log, buildSubagentTools } from "../../../shared"
import { setSessionTools } from "../../../shared/session-tools-store"
import type { SpawnerContext } from "./spawner-context"
import { subagentSessions } from "../../claude-code-session-state"
Expand Down Expand Up @@ -80,12 +80,7 @@ export async function resumeTask(
...(resumeModel ? { model: resumeModel } : {}),
...(resumeVariant ? { variant: resumeVariant } : {}),
tools: (() => {
const tools = {
...getAgentToolRestrictions(task.agent),
task: false,
call_omo_agent: true,
question: false,
}
const tools = buildSubagentTools(task.agent)
setSessionTools(task.sessionID!, tools)
return tools
})(),
Expand Down
9 changes: 2 additions & 7 deletions src/features/background-agent/spawner/task-starter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { QueueItem } from "../constants"
import { log, getAgentToolRestrictions, promptWithModelSuggestionRetry } from "../../../shared"
import { log, buildSubagentTools, promptWithModelSuggestionRetry } from "../../../shared"
import { setSessionTools } from "../../../shared/session-tools-store"
import { subagentSessions } from "../../claude-code-session-state"
import { getTaskToastManager } from "../../task-toast-manager"
Expand Down Expand Up @@ -81,12 +81,7 @@ export async function startTask(item: QueueItem, ctx: SpawnerContext): Promise<v
...(launchVariant ? { variant: launchVariant } : {}),
system: input.skillContent,
tools: (() => {
const tools = {
...getAgentToolRestrictions(input.agent),
task: false,
call_omo_agent: true,
question: false,
}
const tools = buildSubagentTools(input.agent)
setSessionTools(sessionID, tools)
return tools
})(),
Expand Down
9 changes: 2 additions & 7 deletions src/features/background-agent/task-resumer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { log, getAgentToolRestrictions } from "../../shared"
import { log, buildSubagentTools } from "../../shared"
import { subagentSessions } from "../claude-code-session-state"
import { getTaskToastManager } from "../task-toast-manager"

Expand Down Expand Up @@ -111,12 +111,7 @@ export async function resumeBackgroundTask(args: {
agent: existingTask.agent,
...(resumeModel ? { model: resumeModel } : {}),
...(resumeVariant ? { variant: resumeVariant } : {}),
tools: {
...getAgentToolRestrictions(existingTask.agent),
task: false,
call_omo_agent: true,
question: false,
},
tools: buildSubagentTools(existingTask.agent),
parts: [{ type: "text", text: input.prompt }],
},
}).catch((error) => {
Expand Down
9 changes: 2 additions & 7 deletions src/features/background-agent/task-starter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { log, getAgentToolRestrictions, promptWithModelSuggestionRetry } from "../../shared"
import { log, buildSubagentTools, promptWithModelSuggestionRetry } from "../../shared"
import { isInsideTmux } from "../../shared/tmux"

import { subagentSessions } from "../claude-code-session-state"
Expand Down Expand Up @@ -150,12 +150,7 @@ export async function startQueuedTask(args: {
...(launchModel ? { model: launchModel } : {}),
...(launchVariant ? { variant: launchVariant } : {}),
system: input.skillContent,
tools: {
...getAgentToolRestrictions(input.agent),
task: false,
call_omo_agent: true,
question: false,
},
tools: buildSubagentTools(input.agent),
parts: [{ type: "text", text: input.prompt }],
},
}).catch((error) => {
Expand Down
21 changes: 21 additions & 0 deletions src/plugin-handlers/agent-config-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ function hasConfiguredDefaultAgent(config: Record<string, unknown>): boolean {
return typeof defaultAgent === "string" && defaultAgent.trim().length > 0;
}

function addCustomAgentsFromConfig(params: {
config: Record<string, unknown>;
pluginConfig: OhMyOpenCodeConfig;
}): void {
const agents = params.pluginConfig.agents;
if (!agents) return;

const configAgent = params.config.agent as Record<string, unknown> | undefined;
if (!configAgent) return;

for (const [name, agentCfg] of Object.entries(agents)) {
if (agentCfg && !configAgent[name]) {
configAgent[name] = migrateAgentConfig(agentCfg as Record<string, unknown>);
}
}
}

export async function applyAgentConfig(params: {
config: Record<string, unknown>;
pluginConfig: OhMyOpenCodeConfig;
Expand Down Expand Up @@ -192,6 +209,8 @@ export async function applyAgentConfig(params: {
build: { ...migratedBuild, mode: "subagent", hidden: true },
...(planDemoteConfig ? { plan: planDemoteConfig } : {}),
};

addCustomAgentsFromConfig(params);
} else {
params.config.agent = {
...builtinAgents,
Expand All @@ -200,6 +219,8 @@ export async function applyAgentConfig(params: {
...pluginAgents,
...configAgent,
};

addCustomAgentsFromConfig(params);
}

if (params.config.agent) {
Expand Down
10 changes: 10 additions & 0 deletions src/plugin-handlers/tool-config-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { OhMyOpenCodeConfig } from "../config";
import { getAgentDisplayName } from "../shared/agent-display-names";
import { getAgentToolRestrictions } from "../shared/agent-tool-restrictions";

type AgentWithPermission = { permission?: Record<string, unknown> };

Expand Down Expand Up @@ -98,6 +99,15 @@ export function applyToolConfig(params: {
};
}

for (const customAgent of Object.keys(params.agentResult)) {
const agent = params.agentResult[customAgent] as AgentWithPermission | undefined;
if (!agent) continue;
const restrictions = getAgentToolRestrictions(customAgent);
if (!("task" in restrictions) && !agent.permission?.task) {
agent.permission = { ...agent.permission, task: "allow" };
}
}

params.config.permission = {
...(params.config.permission as Record<string, unknown>),
webfetch: "allow",
Expand Down
18 changes: 18 additions & 0 deletions src/shared/agent-tool-restrictions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,21 @@ export function hasAgentToolRestrictions(agentName: string): boolean {
?? Object.entries(AGENT_RESTRICTIONS).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1]
return restrictions !== undefined && Object.keys(restrictions).length > 0
}

/**
* Build the tools restriction object for a subagent.
*
* - Gets base restrictions from AGENT_RESTRICTIONS via getAgentToolRestrictions().
* - If restrictions already define `task`, uses that value (built-in agents stay restricted).
* - If restrictions do NOT define `task` (custom/unknown agents), defaults to `task: true` (allow).
* - Always sets `call_omo_agent: true` and `question: false`.
*/
export function buildSubagentTools(agentName: string): Record<string, boolean> {
const restrictions = getAgentToolRestrictions(agentName)
return {
...restrictions,
...("task" in restrictions ? {} : { task: true }),
call_omo_agent: true,
question: false,
}
}
8 changes: 2 additions & 6 deletions src/tools/call-omo-agent/subagent-session-prompter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PluginInput } from "@opencode-ai/plugin"
import { log, getAgentToolRestrictions } from "../../shared"
import { log, buildSubagentTools } from "../../shared"

export async function promptSubagentSession(
ctx: PluginInput,
Expand All @@ -10,11 +10,7 @@ export async function promptSubagentSession(
path: { id: options.sessionID },
body: {
agent: options.agent,
tools: {
...getAgentToolRestrictions(options.agent),
task: false,
question: false,
},
tools: buildSubagentTools(options.agent),
parts: [{ type: "text", text: options.prompt }],
},
})
Expand Down
8 changes: 2 additions & 6 deletions src/tools/call-omo-agent/sync-executor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CallOmoAgentArgs } from "./types"
import type { PluginInput } from "@opencode-ai/plugin"
import { log } from "../../shared"
import { getAgentToolRestrictions } from "../../shared"
import { buildSubagentTools } from "../../shared"
import { createOrGetSession } from "./session-creator"
import { waitForCompletion } from "./completion-poller"
import { processMessages } from "./message-processor"
Expand Down Expand Up @@ -45,11 +45,7 @@ export async function executeSync(
path: { id: sessionID },
body: {
agent: args.subagent_type,
tools: {
...getAgentToolRestrictions(args.subagent_type),
task: false,
question: false,
},
tools: buildSubagentTools(args.subagent_type),
parts: [{ type: "text", text: args.prompt }],
},
})
Expand Down
11 changes: 2 additions & 9 deletions src/tools/delegate-task/sync-continuation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types"
import type { ExecutorContext, SessionMessage } from "./executor-types"
import { isPlanFamily } from "./constants"
import { storeToolMetadata } from "../../features/tool-metadata-store"
import { getTaskToastManager } from "../../features/task-toast-manager"
import { getAgentToolRestrictions } from "../../shared/agent-tool-restrictions"
import { buildSubagentTools } from "../../shared/agent-tool-restrictions"
import { getMessageDir } from "../../shared"
import { promptWithModelSuggestionRetry } from "../../shared/model-suggestion-retry"
import { findNearestMessageWithFields } from "../../features/hook-message-injector"
Expand Down Expand Up @@ -78,13 +77,7 @@ export async function executeSyncContinuation(
resumeVariant = resumeMessage?.model?.variant
}

const allowTask = isPlanFamily(resumeAgent)
const tools = {
...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}),
task: allowTask,
call_omo_agent: true,
question: false,
}
const tools = buildSubagentTools(resumeAgent ?? "")
setSessionTools(args.session_id!, tools)

await promptWithModelSuggestionRetry(client, {
Expand Down
11 changes: 2 additions & 9 deletions src/tools/delegate-task/sync-prompt-sender.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { DelegateTaskArgs, OpencodeClient } from "./types"
import { isPlanFamily } from "./constants"
import {
promptSyncWithModelSuggestionRetry,
promptWithModelSuggestionRetry,
} from "../../shared/model-suggestion-retry"
import { formatDetailedError } from "./error-formatting"
import { getAgentToolRestrictions } from "../../shared/agent-tool-restrictions"
import { buildSubagentTools } from "../../shared/agent-tool-restrictions"
import { setSessionTools } from "../../shared/session-tools-store"

type SendSyncPromptDeps = {
Expand Down Expand Up @@ -41,13 +40,7 @@ export async function sendSyncPrompt(
},
deps: SendSyncPromptDeps = sendSyncPromptDeps
): Promise<string | null> {
const allowTask = isPlanFamily(input.agentToUse)
const tools = {
task: allowTask,
call_omo_agent: true,
question: false,
...getAgentToolRestrictions(input.agentToUse),
}
const tools = buildSubagentTools(input.agentToUse)
setSessionTools(input.sessionID, tools)

const promptArgs = {
Expand Down