Merge pull request #2005 from code-yeongyu/fix/1803-session-recovery-unavailable-tool
fix(session-recovery): handle unavailable_tool (dummy_tool) errors
This commit is contained in:
commit
92c3d3917b
@ -1,4 +1,4 @@
|
|||||||
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test"
|
import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test"
|
||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import * as originalExecutor from "./executor"
|
import * as originalExecutor from "./executor"
|
||||||
import * as originalParser from "./parser"
|
import * as originalParser from "./parser"
|
||||||
@ -81,6 +81,10 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
|||||||
parseAnthropicTokenLimitErrorMock.mockClear()
|
parseAnthropicTokenLimitErrorMock.mockClear()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
test("cancels pending timer when session.idle handles compaction first", async () => {
|
test("cancels pending timer when session.idle handles compaction first", async () => {
|
||||||
//#given
|
//#given
|
||||||
const { restore, getClearTimeoutCalls } = setupDelayedTimeoutMocks()
|
const { restore, getClearTimeoutCalls } = setupDelayedTimeoutMocks()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/// <reference types="bun-types" />
|
/// <reference types="bun-types" />
|
||||||
import { describe, expect, it } from "bun:test"
|
import { describe, expect, it } from "bun:test"
|
||||||
import { detectErrorType, extractMessageIndex } from "./detect-error-type"
|
import { detectErrorType, extractMessageIndex, extractUnavailableToolName } from "./detect-error-type"
|
||||||
|
|
||||||
describe("detectErrorType", () => {
|
describe("detectErrorType", () => {
|
||||||
it("#given a tool_use/tool_result error #when detecting #then returns tool_result_missing", () => {
|
it("#given a tool_use/tool_result error #when detecting #then returns tool_result_missing", () => {
|
||||||
@ -101,6 +101,56 @@ describe("detectErrorType", () => {
|
|||||||
//#then
|
//#then
|
||||||
expect(result).toBe("tool_result_missing")
|
expect(result).toBe("tool_result_missing")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("#given a dummy_tool unavailable tool error #when detecting #then returns unavailable_tool", () => {
|
||||||
|
//#given
|
||||||
|
const error = { message: "model tried to call unavailable tool 'invalid'" }
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = detectErrorType(error)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toBe("unavailable_tool")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given a no such tool error #when detecting #then returns unavailable_tool", () => {
|
||||||
|
//#given
|
||||||
|
const error = { message: "No such tool: grepppp" }
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = detectErrorType(error)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toBe("unavailable_tool")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given a NoSuchToolError token #when detecting #then returns unavailable_tool", () => {
|
||||||
|
//#given
|
||||||
|
const error = { message: "NoSuchToolError: no such tool invalid" }
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = detectErrorType(error)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toBe("unavailable_tool")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given a dummy_tool token in nested error #when detecting #then returns unavailable_tool", () => {
|
||||||
|
//#given
|
||||||
|
const error = {
|
||||||
|
data: {
|
||||||
|
error: {
|
||||||
|
message: "dummy_tool Model tried to call unavailable tool 'invalid'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = detectErrorType(error)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toBe("unavailable_tool")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("extractMessageIndex", () => {
|
describe("extractMessageIndex", () => {
|
||||||
@ -127,3 +177,38 @@ describe("extractMessageIndex", () => {
|
|||||||
expect(result).toBeNull()
|
expect(result).toBeNull()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("extractUnavailableToolName", () => {
|
||||||
|
it("#given unavailable tool error with quoted tool name #when extracting #then returns tool name", () => {
|
||||||
|
//#given
|
||||||
|
const error = { message: "model tried to call unavailable tool 'invalid'" }
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = extractUnavailableToolName(error)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toBe("invalid")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given error without unavailable tool name #when extracting #then returns null", () => {
|
||||||
|
//#given
|
||||||
|
const error = { message: "dummy_tool appeared without tool name" }
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = extractUnavailableToolName(error)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given no such tool error with colon format #when extracting #then returns tool name", () => {
|
||||||
|
//#given
|
||||||
|
const error = { message: "No such tool: invalid_tool" }
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = extractUnavailableToolName(error)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toBe("invalid_tool")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export type RecoveryErrorType =
|
|||||||
| "thinking_block_order"
|
| "thinking_block_order"
|
||||||
| "thinking_disabled_violation"
|
| "thinking_disabled_violation"
|
||||||
| "assistant_prefill_unsupported"
|
| "assistant_prefill_unsupported"
|
||||||
|
| "unavailable_tool"
|
||||||
| null
|
| null
|
||||||
|
|
||||||
function getErrorMessage(error: unknown): string {
|
function getErrorMessage(error: unknown): string {
|
||||||
@ -43,6 +44,16 @@ export function extractMessageIndex(error: unknown): number | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractUnavailableToolName(error: unknown): string | null {
|
||||||
|
try {
|
||||||
|
const message = getErrorMessage(error)
|
||||||
|
const match = message.match(/(?:unavailable tool|no such tool)[:\s'"]+([^'".\s]+)/)
|
||||||
|
return match ? match[1] : null
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function detectErrorType(error: unknown): RecoveryErrorType {
|
export function detectErrorType(error: unknown): RecoveryErrorType {
|
||||||
try {
|
try {
|
||||||
const message = getErrorMessage(error)
|
const message = getErrorMessage(error)
|
||||||
@ -74,6 +85,16 @@ export function detectErrorType(error: unknown): RecoveryErrorType {
|
|||||||
return "tool_result_missing"
|
return "tool_result_missing"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
message.includes("dummy_tool") ||
|
||||||
|
message.includes("unavailable tool") ||
|
||||||
|
message.includes("model tried to call unavailable") ||
|
||||||
|
message.includes("nosuchtoolerror") ||
|
||||||
|
message.includes("no such tool")
|
||||||
|
) {
|
||||||
|
return "unavailable_tool"
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
} catch {
|
} catch {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { detectErrorType } from "./detect-error-type"
|
|||||||
import type { RecoveryErrorType } from "./detect-error-type"
|
import type { RecoveryErrorType } from "./detect-error-type"
|
||||||
import type { MessageData } from "./types"
|
import type { MessageData } from "./types"
|
||||||
import { recoverToolResultMissing } from "./recover-tool-result-missing"
|
import { recoverToolResultMissing } from "./recover-tool-result-missing"
|
||||||
|
import { recoverUnavailableTool } from "./recover-unavailable-tool"
|
||||||
import { recoverThinkingBlockOrder } from "./recover-thinking-block-order"
|
import { recoverThinkingBlockOrder } from "./recover-thinking-block-order"
|
||||||
import { recoverThinkingDisabledViolation } from "./recover-thinking-disabled-violation"
|
import { recoverThinkingDisabledViolation } from "./recover-thinking-disabled-violation"
|
||||||
import { extractResumeConfig, findLastUserMessage, resumeSession } from "./resume"
|
import { extractResumeConfig, findLastUserMessage, resumeSession } from "./resume"
|
||||||
@ -79,12 +80,14 @@ export function createSessionRecoveryHook(ctx: PluginInput, options?: SessionRec
|
|||||||
|
|
||||||
const toastTitles: Record<RecoveryErrorType & string, string> = {
|
const toastTitles: Record<RecoveryErrorType & string, string> = {
|
||||||
tool_result_missing: "Tool Crash Recovery",
|
tool_result_missing: "Tool Crash Recovery",
|
||||||
|
unavailable_tool: "Tool Recovery",
|
||||||
thinking_block_order: "Thinking Block Recovery",
|
thinking_block_order: "Thinking Block Recovery",
|
||||||
thinking_disabled_violation: "Thinking Strip Recovery",
|
thinking_disabled_violation: "Thinking Strip Recovery",
|
||||||
"assistant_prefill_unsupported": "Prefill Unsupported",
|
"assistant_prefill_unsupported": "Prefill Unsupported",
|
||||||
}
|
}
|
||||||
const toastMessages: Record<RecoveryErrorType & string, string> = {
|
const toastMessages: Record<RecoveryErrorType & string, string> = {
|
||||||
tool_result_missing: "Injecting cancelled tool results...",
|
tool_result_missing: "Injecting cancelled tool results...",
|
||||||
|
unavailable_tool: "Recovering from unavailable tool call...",
|
||||||
thinking_block_order: "Fixing message structure...",
|
thinking_block_order: "Fixing message structure...",
|
||||||
thinking_disabled_violation: "Stripping thinking blocks...",
|
thinking_disabled_violation: "Stripping thinking blocks...",
|
||||||
"assistant_prefill_unsupported": "Prefill not supported; continuing without recovery.",
|
"assistant_prefill_unsupported": "Prefill not supported; continuing without recovery.",
|
||||||
@ -105,6 +108,8 @@ export function createSessionRecoveryHook(ctx: PluginInput, options?: SessionRec
|
|||||||
|
|
||||||
if (errorType === "tool_result_missing") {
|
if (errorType === "tool_result_missing") {
|
||||||
success = await recoverToolResultMissing(ctx.client, sessionID, failedMsg)
|
success = await recoverToolResultMissing(ctx.client, sessionID, failedMsg)
|
||||||
|
} else if (errorType === "unavailable_tool") {
|
||||||
|
success = await recoverUnavailableTool(ctx.client, sessionID, failedMsg)
|
||||||
} else if (errorType === "thinking_block_order") {
|
} else if (errorType === "thinking_block_order") {
|
||||||
success = await recoverThinkingBlockOrder(ctx.client, sessionID, failedMsg, ctx.directory, info.error)
|
success = await recoverThinkingBlockOrder(ctx.client, sessionID, failedMsg, ctx.directory, info.error)
|
||||||
if (success && experimental?.auto_resume) {
|
if (success && experimental?.auto_resume) {
|
||||||
|
|||||||
108
src/hooks/session-recovery/recover-unavailable-tool.ts
Normal file
108
src/hooks/session-recovery/recover-unavailable-tool.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import type { createOpencodeClient } from "@opencode-ai/sdk"
|
||||||
|
import { extractUnavailableToolName } from "./detect-error-type"
|
||||||
|
import { readParts } from "./storage"
|
||||||
|
import type { MessageData } from "./types"
|
||||||
|
import { normalizeSDKResponse } from "../../shared"
|
||||||
|
import { isSqliteBackend } from "../../shared/opencode-storage-detection"
|
||||||
|
|
||||||
|
type Client = ReturnType<typeof createOpencodeClient>
|
||||||
|
|
||||||
|
interface ToolResultPart {
|
||||||
|
type: "tool_result"
|
||||||
|
tool_use_id: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PromptWithToolResultInput {
|
||||||
|
path: { id: string }
|
||||||
|
body: { parts: ToolResultPart[] }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolUsePart {
|
||||||
|
type: "tool_use"
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MessagePart {
|
||||||
|
type: string
|
||||||
|
id?: string
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractToolUseParts(parts: MessagePart[]): ToolUsePart[] {
|
||||||
|
return parts.filter(
|
||||||
|
(part): part is ToolUsePart =>
|
||||||
|
part.type === "tool_use" && typeof part.id === "string" && typeof part.name === "string"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readPartsFromSDKFallback(
|
||||||
|
client: Client,
|
||||||
|
sessionID: string,
|
||||||
|
messageID: string
|
||||||
|
): Promise<MessagePart[]> {
|
||||||
|
try {
|
||||||
|
const response = await client.session.messages({ path: { id: sessionID } })
|
||||||
|
const messages = normalizeSDKResponse(response, [] as MessageData[], { preferResponseOnMissingData: true })
|
||||||
|
const target = messages.find((message) => message.info?.id === messageID)
|
||||||
|
if (!target?.parts) return []
|
||||||
|
|
||||||
|
return target.parts.map((part) => ({
|
||||||
|
type: part.type === "tool" ? "tool_use" : part.type,
|
||||||
|
id: "callID" in part ? (part as { callID?: string }).callID : part.id,
|
||||||
|
name: "name" in part && typeof part.name === "string" ? part.name : ("tool" in part && typeof (part as { tool?: unknown }).tool === "string" ? (part as { tool: string }).tool : undefined),
|
||||||
|
}))
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function recoverUnavailableTool(
|
||||||
|
client: Client,
|
||||||
|
sessionID: string,
|
||||||
|
failedAssistantMsg: MessageData
|
||||||
|
): Promise<boolean> {
|
||||||
|
let parts = failedAssistantMsg.parts || []
|
||||||
|
if (parts.length === 0 && failedAssistantMsg.info?.id) {
|
||||||
|
if (isSqliteBackend()) {
|
||||||
|
parts = await readPartsFromSDKFallback(client, sessionID, failedAssistantMsg.info.id)
|
||||||
|
} else {
|
||||||
|
const storedParts = readParts(failedAssistantMsg.info.id)
|
||||||
|
parts = storedParts.map((part) => ({
|
||||||
|
type: part.type === "tool" ? "tool_use" : part.type,
|
||||||
|
id: "callID" in part ? (part as { callID?: string }).callID : part.id,
|
||||||
|
name: "tool" in part && typeof part.tool === "string" ? part.tool : undefined,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolUseParts = extractToolUseParts(parts)
|
||||||
|
if (toolUseParts.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const unavailableToolName = extractUnavailableToolName(failedAssistantMsg.info?.error)
|
||||||
|
const matchingToolUses = unavailableToolName
|
||||||
|
? toolUseParts.filter((part) => part.name.toLowerCase() === unavailableToolName)
|
||||||
|
: []
|
||||||
|
const targetToolUses = matchingToolUses.length > 0 ? matchingToolUses : toolUseParts
|
||||||
|
|
||||||
|
const toolResultParts = targetToolUses.map((part) => ({
|
||||||
|
type: "tool_result" as const,
|
||||||
|
tool_use_id: part.id,
|
||||||
|
content: '{"status":"error","error":"Tool not available. Please continue without this tool."}',
|
||||||
|
}))
|
||||||
|
|
||||||
|
try {
|
||||||
|
const promptInput: PromptWithToolResultInput = {
|
||||||
|
path: { id: sessionID },
|
||||||
|
body: { parts: toolResultParts },
|
||||||
|
}
|
||||||
|
const promptAsync = client.session.promptAsync as (...args: never[]) => unknown
|
||||||
|
await Reflect.apply(promptAsync, client.session, [promptInput])
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -82,7 +82,7 @@ export async function applyAgentConfig(params: {
|
|||||||
migratedDisabledAgents,
|
migratedDisabledAgents,
|
||||||
params.pluginConfig.agents,
|
params.pluginConfig.agents,
|
||||||
params.ctx.directory,
|
params.ctx.directory,
|
||||||
undefined,
|
currentModel,
|
||||||
params.pluginConfig.categories,
|
params.pluginConfig.categories,
|
||||||
params.pluginConfig.git_master,
|
params.pluginConfig.git_master,
|
||||||
allDiscoveredSkills,
|
allDiscoveredSkills,
|
||||||
|
|||||||
@ -1277,12 +1277,15 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("disable_omo_env pass-through", () => {
|
describe("disable_omo_env pass-through", () => {
|
||||||
test("omits <omo-env> in generated sisyphus prompt when disable_omo_env is true", async () => {
|
test("passes disable_omo_env=true to createBuiltinAgents", async () => {
|
||||||
//#given
|
//#given
|
||||||
;(agents.createBuiltinAgents as any)?.mockRestore?.()
|
const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as {
|
||||||
;(shared.fetchAvailableModels as any).mockResolvedValue(
|
mockResolvedValue: (value: Record<string, unknown>) => void
|
||||||
new Set(["anthropic/claude-opus-4-6", "google/gemini-3-flash"])
|
mock: { calls: unknown[][] }
|
||||||
)
|
}
|
||||||
|
createBuiltinAgentsMock.mockResolvedValue({
|
||||||
|
sisyphus: { name: "sisyphus", prompt: "without-env", mode: "primary" },
|
||||||
|
})
|
||||||
|
|
||||||
const pluginConfig: OhMyOpenCodeConfig = {
|
const pluginConfig: OhMyOpenCodeConfig = {
|
||||||
experimental: { disable_omo_env: true },
|
experimental: { disable_omo_env: true },
|
||||||
@ -1304,18 +1307,21 @@ describe("disable_omo_env pass-through", () => {
|
|||||||
await handler(config)
|
await handler(config)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
const agentResult = config.agent as Record<string, { prompt?: string }>
|
const lastCall =
|
||||||
const sisyphusPrompt = agentResult[getAgentDisplayName("sisyphus")]?.prompt
|
createBuiltinAgentsMock.mock.calls[createBuiltinAgentsMock.mock.calls.length - 1]
|
||||||
expect(sisyphusPrompt).toBeDefined()
|
expect(lastCall).toBeDefined()
|
||||||
expect(sisyphusPrompt).not.toContain("<omo-env>")
|
expect(lastCall?.[12]).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("keeps <omo-env> in generated sisyphus prompt when disable_omo_env is omitted", async () => {
|
test("passes disable_omo_env=false to createBuiltinAgents when omitted", async () => {
|
||||||
//#given
|
//#given
|
||||||
;(agents.createBuiltinAgents as any)?.mockRestore?.()
|
const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as {
|
||||||
;(shared.fetchAvailableModels as any).mockResolvedValue(
|
mockResolvedValue: (value: Record<string, unknown>) => void
|
||||||
new Set(["anthropic/claude-opus-4-6", "google/gemini-3-flash"])
|
mock: { calls: unknown[][] }
|
||||||
)
|
}
|
||||||
|
createBuiltinAgentsMock.mockResolvedValue({
|
||||||
|
sisyphus: { name: "sisyphus", prompt: "with-env", mode: "primary" },
|
||||||
|
})
|
||||||
|
|
||||||
const pluginConfig: OhMyOpenCodeConfig = {}
|
const pluginConfig: OhMyOpenCodeConfig = {}
|
||||||
const config: Record<string, unknown> = {
|
const config: Record<string, unknown> = {
|
||||||
@ -1335,9 +1341,9 @@ describe("disable_omo_env pass-through", () => {
|
|||||||
await handler(config)
|
await handler(config)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
const agentResult = config.agent as Record<string, { prompt?: string }>
|
const lastCall =
|
||||||
const sisyphusPrompt = agentResult[getAgentDisplayName("sisyphus")]?.prompt
|
createBuiltinAgentsMock.mock.calls[createBuiltinAgentsMock.mock.calls.length - 1]
|
||||||
expect(sisyphusPrompt).toBeDefined()
|
expect(lastCall).toBeDefined()
|
||||||
expect(sisyphusPrompt).toContain("<omo-env>")
|
expect(lastCall?.[12]).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -66,7 +66,7 @@ describe("createChatHeadersHandler", () => {
|
|||||||
sessionID: "ses_1",
|
sessionID: "ses_1",
|
||||||
provider: { id: "openai" },
|
provider: { id: "openai" },
|
||||||
message: {
|
message: {
|
||||||
id: "msg_1",
|
id: "msg_2",
|
||||||
role: "user",
|
role: "user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import {
|
|||||||
createInteractiveBashSessionHook,
|
createInteractiveBashSessionHook,
|
||||||
createRalphLoopHook,
|
createRalphLoopHook,
|
||||||
createEditErrorRecoveryHook,
|
createEditErrorRecoveryHook,
|
||||||
createJsonErrorRecoveryHook,
|
|
||||||
createDelegateTaskRetryHook,
|
createDelegateTaskRetryHook,
|
||||||
createTaskResumeInfoHook,
|
createTaskResumeInfoHook,
|
||||||
createStartWorkHook,
|
createStartWorkHook,
|
||||||
@ -51,7 +50,6 @@ export type SessionHooks = {
|
|||||||
interactiveBashSession: ReturnType<typeof createInteractiveBashSessionHook> | null
|
interactiveBashSession: ReturnType<typeof createInteractiveBashSessionHook> | null
|
||||||
ralphLoop: ReturnType<typeof createRalphLoopHook> | null
|
ralphLoop: ReturnType<typeof createRalphLoopHook> | null
|
||||||
editErrorRecovery: ReturnType<typeof createEditErrorRecoveryHook> | null
|
editErrorRecovery: ReturnType<typeof createEditErrorRecoveryHook> | null
|
||||||
jsonErrorRecovery: ReturnType<typeof createJsonErrorRecoveryHook> | null
|
|
||||||
delegateTaskRetry: ReturnType<typeof createDelegateTaskRetryHook> | null
|
delegateTaskRetry: ReturnType<typeof createDelegateTaskRetryHook> | null
|
||||||
startWork: ReturnType<typeof createStartWorkHook> | null
|
startWork: ReturnType<typeof createStartWorkHook> | null
|
||||||
prometheusMdOnly: ReturnType<typeof createPrometheusMdOnlyHook> | null
|
prometheusMdOnly: ReturnType<typeof createPrometheusMdOnlyHook> | null
|
||||||
@ -212,10 +210,6 @@ export function createSessionHooks(args: {
|
|||||||
? safeHook("edit-error-recovery", () => createEditErrorRecoveryHook(ctx))
|
? safeHook("edit-error-recovery", () => createEditErrorRecoveryHook(ctx))
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const jsonErrorRecovery = isHookEnabled("json-error-recovery")
|
|
||||||
? safeHook("json-error-recovery", () => createJsonErrorRecoveryHook(ctx))
|
|
||||||
: null
|
|
||||||
|
|
||||||
const delegateTaskRetry = isHookEnabled("delegate-task-retry")
|
const delegateTaskRetry = isHookEnabled("delegate-task-retry")
|
||||||
? safeHook("delegate-task-retry", () => createDelegateTaskRetryHook(ctx))
|
? safeHook("delegate-task-retry", () => createDelegateTaskRetryHook(ctx))
|
||||||
: null
|
: null
|
||||||
@ -268,7 +262,6 @@ export function createSessionHooks(args: {
|
|||||||
interactiveBashSession,
|
interactiveBashSession,
|
||||||
ralphLoop,
|
ralphLoop,
|
||||||
editErrorRecovery,
|
editErrorRecovery,
|
||||||
jsonErrorRecovery,
|
|
||||||
delegateTaskRetry,
|
delegateTaskRetry,
|
||||||
startWork,
|
startWork,
|
||||||
prometheusMdOnly,
|
prometheusMdOnly,
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
createTasksTodowriteDisablerHook,
|
createTasksTodowriteDisablerHook,
|
||||||
createWriteExistingFileGuardHook,
|
createWriteExistingFileGuardHook,
|
||||||
createHashlineReadEnhancerHook,
|
createHashlineReadEnhancerHook,
|
||||||
createHashlineEditDiffEnhancerHook,
|
|
||||||
} from "../../hooks"
|
} from "../../hooks"
|
||||||
import {
|
import {
|
||||||
getOpenCodeVersion,
|
getOpenCodeVersion,
|
||||||
@ -32,7 +31,6 @@ export type ToolGuardHooks = {
|
|||||||
tasksTodowriteDisabler: ReturnType<typeof createTasksTodowriteDisablerHook> | null
|
tasksTodowriteDisabler: ReturnType<typeof createTasksTodowriteDisablerHook> | null
|
||||||
writeExistingFileGuard: ReturnType<typeof createWriteExistingFileGuardHook> | null
|
writeExistingFileGuard: ReturnType<typeof createWriteExistingFileGuardHook> | null
|
||||||
hashlineReadEnhancer: ReturnType<typeof createHashlineReadEnhancerHook> | null
|
hashlineReadEnhancer: ReturnType<typeof createHashlineReadEnhancerHook> | null
|
||||||
hashlineEditDiffEnhancer: ReturnType<typeof createHashlineEditDiffEnhancerHook> | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createToolGuardHooks(args: {
|
export function createToolGuardHooks(args: {
|
||||||
@ -101,10 +99,6 @@ export function createToolGuardHooks(args: {
|
|||||||
? safeHook("hashline-read-enhancer", () => createHashlineReadEnhancerHook(ctx, { hashline_edit: { enabled: pluginConfig.hashline_edit ?? true } }))
|
? safeHook("hashline-read-enhancer", () => createHashlineReadEnhancerHook(ctx, { hashline_edit: { enabled: pluginConfig.hashline_edit ?? true } }))
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const hashlineEditDiffEnhancer = isHookEnabled("hashline-edit-diff-enhancer")
|
|
||||||
? safeHook("hashline-edit-diff-enhancer", () => createHashlineEditDiffEnhancerHook({ hashline_edit: { enabled: pluginConfig.hashline_edit ?? true } }))
|
|
||||||
: null
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
commentChecker,
|
commentChecker,
|
||||||
toolOutputTruncator,
|
toolOutputTruncator,
|
||||||
@ -115,6 +109,5 @@ export function createToolGuardHooks(args: {
|
|||||||
tasksTodowriteDisabler,
|
tasksTodowriteDisabler,
|
||||||
writeExistingFileGuard,
|
writeExistingFileGuard,
|
||||||
hashlineReadEnhancer,
|
hashlineReadEnhancer,
|
||||||
hashlineEditDiffEnhancer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,11 +40,9 @@ export function createToolExecuteAfterHandler(args: {
|
|||||||
await hooks.categorySkillReminder?.["tool.execute.after"]?.(input, output)
|
await hooks.categorySkillReminder?.["tool.execute.after"]?.(input, output)
|
||||||
await hooks.interactiveBashSession?.["tool.execute.after"]?.(input, output)
|
await hooks.interactiveBashSession?.["tool.execute.after"]?.(input, output)
|
||||||
await hooks.editErrorRecovery?.["tool.execute.after"]?.(input, output)
|
await hooks.editErrorRecovery?.["tool.execute.after"]?.(input, output)
|
||||||
await hooks.jsonErrorRecovery?.["tool.execute.after"]?.(input, output)
|
|
||||||
await hooks.delegateTaskRetry?.["tool.execute.after"]?.(input, output)
|
await hooks.delegateTaskRetry?.["tool.execute.after"]?.(input, output)
|
||||||
await hooks.atlasHook?.["tool.execute.after"]?.(input, output)
|
await hooks.atlasHook?.["tool.execute.after"]?.(input, output)
|
||||||
await hooks.taskResumeInfo?.["tool.execute.after"]?.(input, output)
|
await hooks.taskResumeInfo?.["tool.execute.after"]?.(input, output)
|
||||||
await hooks.hashlineReadEnhancer?.["tool.execute.after"]?.(input, output)
|
await hooks.hashlineReadEnhancer?.["tool.execute.after"]?.(input, output)
|
||||||
await hooks.hashlineEditDiffEnhancer?.["tool.execute.after"]?.(input, output)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,6 @@ export function createToolExecuteBeforeHandler(args: {
|
|||||||
await hooks.prometheusMdOnly?.["tool.execute.before"]?.(input, output)
|
await hooks.prometheusMdOnly?.["tool.execute.before"]?.(input, output)
|
||||||
await hooks.sisyphusJuniorNotepad?.["tool.execute.before"]?.(input, output)
|
await hooks.sisyphusJuniorNotepad?.["tool.execute.before"]?.(input, output)
|
||||||
await hooks.atlasHook?.["tool.execute.before"]?.(input, output)
|
await hooks.atlasHook?.["tool.execute.before"]?.(input, output)
|
||||||
await hooks.hashlineEditDiffEnhancer?.["tool.execute.before"]?.(input, output)
|
|
||||||
if (input.tool === "task") {
|
if (input.tool === "task") {
|
||||||
const argsObject = output.args
|
const argsObject = output.args
|
||||||
const category = typeof argsObject.category === "string" ? argsObject.category : undefined
|
const category = typeof argsObject.category === "string" ? argsObject.category : undefined
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user