diff --git a/src/agents/atlas/agent.ts b/src/agents/atlas/agent.ts
index c583ada5a..ccf987754 100644
--- a/src/agents/atlas/agent.ts
+++ b/src/agents/atlas/agent.ts
@@ -29,7 +29,7 @@ import {
buildDecisionMatrix,
} from "./prompt-section-builder"
-const MODE: AgentMode = "primary"
+const MODE: AgentMode = "all"
export type AtlasPromptSource = "default" | "gpt" | "gemini"
diff --git a/src/agents/hephaestus.ts b/src/agents/hephaestus.ts
index 10f700662..e182c96f4 100644
--- a/src/agents/hephaestus.ts
+++ b/src/agents/hephaestus.ts
@@ -19,7 +19,7 @@ import {
categorizeTools,
} from "./dynamic-agent-prompt-builder";
-const MODE: AgentMode = "primary";
+const MODE: AgentMode = "all";
function buildTodoDisciplineSection(useTaskSystem: boolean): string {
if (useTaskSystem) {
diff --git a/src/agents/sisyphus.ts b/src/agents/sisyphus.ts
index 72173bd48..06debf111 100644
--- a/src/agents/sisyphus.ts
+++ b/src/agents/sisyphus.ts
@@ -8,7 +8,7 @@ import {
buildGeminiIntentGateEnforcement,
} from "./sisyphus-gemini-overlays";
-const MODE: AgentMode = "primary";
+const MODE: AgentMode = "all";
export const SISYPHUS_PROMPT_METADATA: AgentPromptMetadata = {
category: "utility",
cost: "EXPENSIVE",
diff --git a/src/config/schema/oh-my-opencode-config.ts b/src/config/schema/oh-my-opencode-config.ts
index b36e8688f..52e7d461e 100644
--- a/src/config/schema/oh-my-opencode-config.ts
+++ b/src/config/schema/oh-my-opencode-config.ts
@@ -27,7 +27,7 @@ export const OhMyOpenCodeConfigSchema = z.object({
/** Default agent name for `oh-my-opencode run` (env: OPENCODE_DEFAULT_AGENT) */
default_run_agent: z.string().optional(),
disabled_mcps: z.array(AnyMcpNameSchema).optional(),
- disabled_agents: z.array(BuiltinAgentNameSchema).optional(),
+ disabled_agents: z.array(z.string()).optional(),
disabled_skills: z.array(BuiltinSkillNameSchema).optional(),
disabled_hooks: z.array(z.string()).optional(),
disabled_commands: z.array(BuiltinCommandNameSchema).optional(),
diff --git a/src/hooks/todo-continuation-enforcer/idle-event.ts b/src/hooks/todo-continuation-enforcer/idle-event.ts
index 10708d1a3..1f944db59 100644
--- a/src/hooks/todo-continuation-enforcer/idle-event.ts
+++ b/src/hooks/todo-continuation-enforcer/idle-event.ts
@@ -15,6 +15,7 @@ import {
MAX_CONSECUTIVE_FAILURES,
} from "./constants"
import { isLastAssistantMessageAborted } from "./abort-detection"
+import { hasUnansweredQuestion } from "./pending-question-detection"
import { getIncompleteCount } from "./todo"
import type { MessageInfo, ResolvedMessageInfo, Todo } from "./types"
import type { SessionStateStore } from "./session-state"
@@ -74,6 +75,10 @@ export async function handleSessionIdle(args: {
log(`[${HOOK_NAME}] Skipped: last assistant message was aborted (API fallback)`, { sessionID })
return
}
+ if (hasUnansweredQuestion(messages)) {
+ log(`[${HOOK_NAME}] Skipped: pending question awaiting user response`, { sessionID })
+ return
+ }
} catch (error) {
log(`[${HOOK_NAME}] Messages fetch failed, continuing`, { sessionID, error: String(error) })
}
diff --git a/src/hooks/todo-continuation-enforcer/pending-question-detection.test.ts b/src/hooks/todo-continuation-enforcer/pending-question-detection.test.ts
new file mode 100644
index 000000000..5ea4b214c
--- /dev/null
+++ b/src/hooks/todo-continuation-enforcer/pending-question-detection.test.ts
@@ -0,0 +1,100 @@
+///
+import { describe, expect, test } from "bun:test"
+
+import { hasUnansweredQuestion } from "./pending-question-detection"
+
+describe("hasUnansweredQuestion", () => {
+ test("given empty messages, returns false", () => {
+ expect(hasUnansweredQuestion([])).toBe(false)
+ })
+
+ test("given null-ish input, returns false", () => {
+ expect(hasUnansweredQuestion(undefined as never)).toBe(false)
+ })
+
+ test("given last assistant message with question tool_use, returns true", () => {
+ const messages = [
+ { info: { role: "user" } },
+ {
+ info: { role: "assistant" },
+ parts: [
+ { type: "tool_use", name: "question" },
+ ],
+ },
+ ]
+ expect(hasUnansweredQuestion(messages)).toBe(true)
+ })
+
+ test("given last assistant message with question tool-invocation, returns true", () => {
+ const messages = [
+ { info: { role: "user" } },
+ {
+ info: { role: "assistant" },
+ parts: [
+ { type: "tool-invocation", toolName: "question" },
+ ],
+ },
+ ]
+ expect(hasUnansweredQuestion(messages)).toBe(true)
+ })
+
+ test("given user message after question (answered), returns false", () => {
+ const messages = [
+ {
+ info: { role: "assistant" },
+ parts: [
+ { type: "tool_use", name: "question" },
+ ],
+ },
+ { info: { role: "user" } },
+ ]
+ expect(hasUnansweredQuestion(messages)).toBe(false)
+ })
+
+ test("given assistant message with non-question tool, returns false", () => {
+ const messages = [
+ { info: { role: "user" } },
+ {
+ info: { role: "assistant" },
+ parts: [
+ { type: "tool_use", name: "bash" },
+ ],
+ },
+ ]
+ expect(hasUnansweredQuestion(messages)).toBe(false)
+ })
+
+ test("given assistant message with no parts, returns false", () => {
+ const messages = [
+ { info: { role: "user" } },
+ { info: { role: "assistant" } },
+ ]
+ expect(hasUnansweredQuestion(messages)).toBe(false)
+ })
+
+ test("given role on message directly (not in info), returns true for question", () => {
+ const messages = [
+ { role: "user" },
+ {
+ role: "assistant",
+ parts: [
+ { type: "tool_use", name: "question" },
+ ],
+ },
+ ]
+ expect(hasUnansweredQuestion(messages)).toBe(true)
+ })
+
+ test("given mixed tools including question, returns true", () => {
+ const messages = [
+ {
+ info: { role: "assistant" },
+ parts: [
+ { type: "tool_use", name: "bash" },
+ { type: "tool_use", name: "question" },
+ ],
+ },
+ ]
+ expect(hasUnansweredQuestion(messages)).toBe(true)
+ })
+})
diff --git a/src/hooks/todo-continuation-enforcer/pending-question-detection.ts b/src/hooks/todo-continuation-enforcer/pending-question-detection.ts
new file mode 100644
index 000000000..fd97b6c35
--- /dev/null
+++ b/src/hooks/todo-continuation-enforcer/pending-question-detection.ts
@@ -0,0 +1,40 @@
+import { log } from "../../shared/logger"
+import { HOOK_NAME } from "./constants"
+
+interface MessagePart {
+ type: string
+ name?: string
+ toolName?: string
+}
+
+interface Message {
+ info?: { role?: string }
+ role?: string
+ parts?: MessagePart[]
+}
+
+export function hasUnansweredQuestion(messages: Message[]): boolean {
+ if (!messages || messages.length === 0) return false
+
+ for (let i = messages.length - 1; i >= 0; i--) {
+ const msg = messages[i]
+ const role = msg.info?.role ?? msg.role
+
+ if (role === "user") return false
+
+ if (role === "assistant" && msg.parts) {
+ const hasQuestion = msg.parts.some(
+ (part) =>
+ (part.type === "tool_use" || part.type === "tool-invocation") &&
+ (part.name === "question" || part.toolName === "question"),
+ )
+ if (hasQuestion) {
+ log(`[${HOOK_NAME}] Detected pending question tool in last assistant message`)
+ return true
+ }
+ return false
+ }
+ }
+
+ return false
+}
diff --git a/src/plugin-handlers/agent-config-handler.ts b/src/plugin-handlers/agent-config-handler.ts
index c5d59e149..08230f55a 100644
--- a/src/plugin-handlers/agent-config-handler.ts
+++ b/src/plugin-handlers/agent-config-handler.ts
@@ -106,6 +106,15 @@ export async function applyAgentConfig(params: {
]),
);
+ const disabledAgentNames = new Set(
+ (migratedDisabledAgents ?? []).map(a => a.toLowerCase())
+ );
+
+ const filterDisabledAgents = (agents: Record) =>
+ Object.fromEntries(
+ Object.entries(agents).filter(([name]) => !disabledAgentNames.has(name.toLowerCase()))
+ );
+
const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true;
const builderEnabled =
params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
@@ -194,9 +203,9 @@ export async function applyAgentConfig(params: {
...Object.fromEntries(
Object.entries(builtinAgents).filter(([key]) => key !== "sisyphus"),
),
- ...userAgents,
- ...projectAgents,
- ...pluginAgents,
+ ...filterDisabledAgents(userAgents),
+ ...filterDisabledAgents(projectAgents),
+ ...filterDisabledAgents(pluginAgents),
...filteredConfigAgents,
build: { ...migratedBuild, mode: "subagent", hidden: true },
...(planDemoteConfig ? { plan: planDemoteConfig } : {}),
@@ -204,9 +213,9 @@ export async function applyAgentConfig(params: {
} else {
params.config.agent = {
...builtinAgents,
- ...userAgents,
- ...projectAgents,
- ...pluginAgents,
+ ...filterDisabledAgents(userAgents),
+ ...filterDisabledAgents(projectAgents),
+ ...filterDisabledAgents(pluginAgents),
...configAgent,
};
}