Merge pull request #1963 from MoerAI/fix/multi-issue-1888-1693-1891
fix: resolve issues #1888, #1693, #1891
This commit is contained in:
commit
2ece7c3d0a
@ -29,7 +29,7 @@ import {
|
|||||||
buildDecisionMatrix,
|
buildDecisionMatrix,
|
||||||
} from "./prompt-section-builder"
|
} from "./prompt-section-builder"
|
||||||
|
|
||||||
const MODE: AgentMode = "primary"
|
const MODE: AgentMode = "all"
|
||||||
|
|
||||||
export type AtlasPromptSource = "default" | "gpt" | "gemini"
|
export type AtlasPromptSource = "default" | "gpt" | "gemini"
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import {
|
|||||||
categorizeTools,
|
categorizeTools,
|
||||||
} from "./dynamic-agent-prompt-builder";
|
} from "./dynamic-agent-prompt-builder";
|
||||||
|
|
||||||
const MODE: AgentMode = "primary";
|
const MODE: AgentMode = "all";
|
||||||
|
|
||||||
function buildTodoDisciplineSection(useTaskSystem: boolean): string {
|
function buildTodoDisciplineSection(useTaskSystem: boolean): string {
|
||||||
if (useTaskSystem) {
|
if (useTaskSystem) {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
buildGeminiIntentGateEnforcement,
|
buildGeminiIntentGateEnforcement,
|
||||||
} from "./sisyphus-gemini-overlays";
|
} from "./sisyphus-gemini-overlays";
|
||||||
|
|
||||||
const MODE: AgentMode = "primary";
|
const MODE: AgentMode = "all";
|
||||||
export const SISYPHUS_PROMPT_METADATA: AgentPromptMetadata = {
|
export const SISYPHUS_PROMPT_METADATA: AgentPromptMetadata = {
|
||||||
category: "utility",
|
category: "utility",
|
||||||
cost: "EXPENSIVE",
|
cost: "EXPENSIVE",
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export const OhMyOpenCodeConfigSchema = z.object({
|
|||||||
/** Default agent name for `oh-my-opencode run` (env: OPENCODE_DEFAULT_AGENT) */
|
/** Default agent name for `oh-my-opencode run` (env: OPENCODE_DEFAULT_AGENT) */
|
||||||
default_run_agent: z.string().optional(),
|
default_run_agent: z.string().optional(),
|
||||||
disabled_mcps: z.array(AnyMcpNameSchema).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_skills: z.array(BuiltinSkillNameSchema).optional(),
|
||||||
disabled_hooks: z.array(z.string()).optional(),
|
disabled_hooks: z.array(z.string()).optional(),
|
||||||
disabled_commands: z.array(BuiltinCommandNameSchema).optional(),
|
disabled_commands: z.array(BuiltinCommandNameSchema).optional(),
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
MAX_CONSECUTIVE_FAILURES,
|
MAX_CONSECUTIVE_FAILURES,
|
||||||
} from "./constants"
|
} from "./constants"
|
||||||
import { isLastAssistantMessageAborted } from "./abort-detection"
|
import { isLastAssistantMessageAborted } from "./abort-detection"
|
||||||
|
import { hasUnansweredQuestion } from "./pending-question-detection"
|
||||||
import { getIncompleteCount } from "./todo"
|
import { getIncompleteCount } from "./todo"
|
||||||
import type { MessageInfo, ResolvedMessageInfo, Todo } from "./types"
|
import type { MessageInfo, ResolvedMessageInfo, Todo } from "./types"
|
||||||
import type { SessionStateStore } from "./session-state"
|
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 })
|
log(`[${HOOK_NAME}] Skipped: last assistant message was aborted (API fallback)`, { sessionID })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (hasUnansweredQuestion(messages)) {
|
||||||
|
log(`[${HOOK_NAME}] Skipped: pending question awaiting user response`, { sessionID })
|
||||||
|
return
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`[${HOOK_NAME}] Messages fetch failed, continuing`, { sessionID, error: String(error) })
|
log(`[${HOOK_NAME}] Messages fetch failed, continuing`, { sessionID, error: String(error) })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,100 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -106,6 +106,15 @@ export async function applyAgentConfig(params: {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const disabledAgentNames = new Set(
|
||||||
|
(migratedDisabledAgents ?? []).map(a => a.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterDisabledAgents = (agents: Record<string, unknown>) =>
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(agents).filter(([name]) => !disabledAgentNames.has(name.toLowerCase()))
|
||||||
|
);
|
||||||
|
|
||||||
const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true;
|
const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true;
|
||||||
const builderEnabled =
|
const builderEnabled =
|
||||||
params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
|
params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
|
||||||
@ -194,9 +203,9 @@ export async function applyAgentConfig(params: {
|
|||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(builtinAgents).filter(([key]) => key !== "sisyphus"),
|
Object.entries(builtinAgents).filter(([key]) => key !== "sisyphus"),
|
||||||
),
|
),
|
||||||
...userAgents,
|
...filterDisabledAgents(userAgents),
|
||||||
...projectAgents,
|
...filterDisabledAgents(projectAgents),
|
||||||
...pluginAgents,
|
...filterDisabledAgents(pluginAgents),
|
||||||
...filteredConfigAgents,
|
...filteredConfigAgents,
|
||||||
build: { ...migratedBuild, mode: "subagent", hidden: true },
|
build: { ...migratedBuild, mode: "subagent", hidden: true },
|
||||||
...(planDemoteConfig ? { plan: planDemoteConfig } : {}),
|
...(planDemoteConfig ? { plan: planDemoteConfig } : {}),
|
||||||
@ -204,9 +213,9 @@ export async function applyAgentConfig(params: {
|
|||||||
} else {
|
} else {
|
||||||
params.config.agent = {
|
params.config.agent = {
|
||||||
...builtinAgents,
|
...builtinAgents,
|
||||||
...userAgents,
|
...filterDisabledAgents(userAgents),
|
||||||
...projectAgents,
|
...filterDisabledAgents(projectAgents),
|
||||||
...pluginAgents,
|
...filterDisabledAgents(pluginAgents),
|
||||||
...configAgent,
|
...configAgent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user