fix(prompt): treat post-dispatch failures as accepted
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
findNearestMessageWithFieldsFromSDK,
|
||||
} from "../../features/hook-message-injector"
|
||||
import { dispatchInternalPrompt, isInternalPromptDispatchAccepted } from "../shared/prompt-async-gate"
|
||||
import { isAmbiguousPostDispatchPromptFailure } from "../../shared/prompt-failure-classifier"
|
||||
|
||||
export async function runAggressiveTruncationStrategy(params: {
|
||||
sessionID: string
|
||||
@@ -108,6 +109,13 @@ export async function runAggressiveTruncationStrategy(params: {
|
||||
} as never,
|
||||
})
|
||||
if (!isInternalPromptDispatchAccepted(promptResult)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
log("[auto-compact] delayed auto prompt may have been accepted before ambiguous failure", {
|
||||
sessionID: params.sessionID,
|
||||
error: String(promptResult.error),
|
||||
})
|
||||
return
|
||||
}
|
||||
log("[auto-compact] delayed auto prompt skipped by promptAsync gate", {
|
||||
sessionID: params.sessionID,
|
||||
status: promptResult.status,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { stripAgentListSortPrefix } from "../../shared/agent-display-names"
|
||||
import { log } from "../../shared/logger"
|
||||
import { createInternalAgentContinuationTextPart, resolveInheritedPromptTools } from "../../shared"
|
||||
import { isAmbiguousPromptDispatchFailure } from "../../shared/prompt-failure-classifier"
|
||||
import { isAmbiguousPostDispatchPromptFailure } from "../../shared/prompt-failure-classifier"
|
||||
import { dispatchInternalPrompt, isInternalPromptDispatchAccepted } from "../shared/prompt-async-gate"
|
||||
import { HOOK_NAME } from "./hook-name"
|
||||
import { BOULDER_CONTINUATION_PROMPT } from "./system-reminder-templates"
|
||||
@@ -114,6 +114,15 @@ export async function injectBoulderContinuation(input: {
|
||||
},
|
||||
})
|
||||
if (promptResult.status === "failed") {
|
||||
if (isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
sessionState.promptFailureCount = 0
|
||||
markContinuationInjectedAwaitingToolProgress(sessionState)
|
||||
log(`[${HOOK_NAME}] Boulder continuation prompt failed after dispatch may have been accepted`, {
|
||||
sessionID,
|
||||
error: String(promptResult.error),
|
||||
})
|
||||
return "injected"
|
||||
}
|
||||
throw promptResult.error
|
||||
}
|
||||
if (!isInternalPromptDispatchAccepted(promptResult)) {
|
||||
@@ -129,15 +138,6 @@ export async function injectBoulderContinuation(input: {
|
||||
log(`[${HOOK_NAME}] Boulder continuation injected`, { sessionID })
|
||||
return "injected"
|
||||
} catch (err) {
|
||||
if (isAmbiguousPromptDispatchFailure(err)) {
|
||||
sessionState.promptFailureCount = 0
|
||||
markContinuationInjectedAwaitingToolProgress(sessionState)
|
||||
log(`[${HOOK_NAME}] Boulder continuation prompt failed after dispatch may have been accepted`, {
|
||||
sessionID,
|
||||
error: String(err),
|
||||
})
|
||||
return "injected"
|
||||
}
|
||||
sessionState.promptFailureCount += 1
|
||||
sessionState.lastFailureAt = Date.now()
|
||||
log(`[${HOOK_NAME}] Boulder continuation failed`, {
|
||||
|
||||
@@ -19,7 +19,7 @@ import { isSessionInBoulderLineage } from "./boulder-session-lineage"
|
||||
import { createInternalAgentContinuationTextPart } from "../../shared"
|
||||
import { getAgentConfigKey } from "../../shared/agent-display-names"
|
||||
import { log } from "../../shared/logger"
|
||||
import { isAmbiguousPromptDispatchFailure } from "../../shared/prompt-failure-classifier"
|
||||
import { isAmbiguousPostDispatchPromptFailure } from "../../shared/prompt-failure-classifier"
|
||||
import { shouldPromptAfterSessionIdle } from "../shared/session-idle-settle"
|
||||
import { dispatchInternalPrompt, isInternalPromptDispatchAccepted } from "../shared/prompt-async-gate"
|
||||
import { injectBoulderContinuation } from "./boulder-continuation-injector"
|
||||
@@ -316,7 +316,7 @@ export async function handleAtlasSessionIdle(input: {
|
||||
},
|
||||
})
|
||||
if (!isInternalPromptDispatchAccepted(promptResult)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPromptDispatchFailure(promptResult.error)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
sessionState.boulderCompletionNudgedAt = {
|
||||
...(sessionState.boulderCompletionNudgedAt ?? {}),
|
||||
[work.work_id]: Date.now(),
|
||||
|
||||
@@ -8,6 +8,7 @@ import { clearToolInputCache, stopToolInputCacheCleanup } from "../tool-input-ca
|
||||
import type { PluginConfig } from "../types"
|
||||
import { createInternalAgentTextPart, isHookDisabled, log } from "../../../shared"
|
||||
import { resolveSessionEventID } from "../../../shared/event-session-id"
|
||||
import { isAmbiguousPostDispatchPromptFailure } from "../../../shared/prompt-failure-classifier"
|
||||
import { dispatchInternalPrompt, isInternalPromptDispatchAccepted } from "../../../shared/prompt-async-gate"
|
||||
import {
|
||||
clearAllSessionHookState,
|
||||
@@ -124,7 +125,14 @@ export function createSessionEventHandler(
|
||||
},
|
||||
})
|
||||
if (promptResult.status === "failed") {
|
||||
log("Failed to inject prompt from Stop hook", { error: String(promptResult.error) })
|
||||
if (isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
log("Prompt injected from Stop hook may have been accepted before ambiguous failure", {
|
||||
sessionID,
|
||||
error: String(promptResult.error),
|
||||
})
|
||||
} else {
|
||||
log("Failed to inject prompt from Stop hook", { error: String(promptResult.error) })
|
||||
}
|
||||
} else if (!isInternalPromptDispatchAccepted(promptResult)) {
|
||||
log("Skipped prompt injection from Stop hook", { sessionID, status: promptResult.status })
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../../shared/compaction-agent-config-checkpoint"
|
||||
import { createInternalAgentContinuationTextPart } from "../../shared/internal-initiator-marker"
|
||||
import { log } from "../../shared/logger"
|
||||
import { isAmbiguousPromptDispatchFailure } from "../../shared/prompt-failure-classifier"
|
||||
import { isAmbiguousPostDispatchPromptFailure } from "../../shared/prompt-failure-classifier"
|
||||
import { setSessionModel } from "../../shared/session-model-state"
|
||||
import { setSessionTools } from "../../shared/session-tools-store"
|
||||
import {
|
||||
@@ -102,7 +102,7 @@ export function createRecoveryLogic(
|
||||
},
|
||||
})
|
||||
if (!isInternalPromptDispatchAccepted(promptResult)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPromptDispatchFailure(promptResult.error)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
tailState.lastRecoveryAt = now
|
||||
}
|
||||
log(`[compaction-context-injector] Recovery skipped by promptAsync gate`, {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getMessageDir } from "./message-storage-directory"
|
||||
import { withTimeout } from "./with-timeout"
|
||||
import {
|
||||
createInternalAgentContinuationTextPart,
|
||||
isAmbiguousPromptDispatchFailure,
|
||||
isAmbiguousPostDispatchPromptFailure,
|
||||
isRecord,
|
||||
normalizeSDKResponse,
|
||||
resolveInheritedPromptTools,
|
||||
@@ -160,7 +160,7 @@ export async function injectContinuationPrompt(
|
||||
},
|
||||
})
|
||||
if (promptResult.status === "failed") {
|
||||
if (isAmbiguousPromptDispatchFailure(promptResult.error)) {
|
||||
if (isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
return { status: "dispatched" }
|
||||
}
|
||||
throw promptResult.error
|
||||
@@ -179,9 +179,6 @@ export async function injectContinuationPrompt(
|
||||
}
|
||||
response = promptResult.response
|
||||
} catch (error) {
|
||||
if (isAmbiguousPromptDispatchFailure(error)) {
|
||||
return { status: "dispatched" }
|
||||
}
|
||||
const promptError = error instanceof Error
|
||||
? error
|
||||
: createPromptAsyncError("promptAsync rejected", error)
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
import type { MessageData, ResumeConfig } from "./types"
|
||||
import { readParts } from "./storage/parts-reader"
|
||||
import { isSqliteBackend } from "../../shared/opencode-storage-detection"
|
||||
import { isAmbiguousPromptDispatchFailure, normalizeSDKResponse } from "../../shared"
|
||||
import { isAmbiguousPostDispatchPromptFailure, normalizeSDKResponse } from "../../shared"
|
||||
import { dispatchInternalPrompt, isInternalPromptDispatchAccepted } from "../shared/prompt-async-gate"
|
||||
|
||||
type Client = ReturnType<typeof createOpencodeClient>
|
||||
@@ -179,7 +179,7 @@ export async function recoverToolResultMissing(
|
||||
queueBehavior: "defer",
|
||||
})
|
||||
|
||||
if (promptResult.status === "failed" && isAmbiguousPromptDispatchFailure(promptResult.error)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
return true
|
||||
}
|
||||
return isInternalPromptDispatchAccepted(promptResult)
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
import { extractUnavailableToolName } from "./detect-error-type"
|
||||
import { readParts } from "./storage"
|
||||
import type { MessageData } from "./types"
|
||||
import { isAmbiguousPromptDispatchFailure, normalizeSDKResponse } from "../../shared"
|
||||
import { isAmbiguousPostDispatchPromptFailure, normalizeSDKResponse } from "../../shared"
|
||||
import { isSqliteBackend } from "../../shared/opencode-storage-detection"
|
||||
import { dispatchInternalPrompt, isInternalPromptDispatchAccepted } from "../shared/prompt-async-gate"
|
||||
|
||||
@@ -130,7 +130,7 @@ export async function recoverUnavailableTool(
|
||||
checkToolState: false,
|
||||
input: promptInput,
|
||||
})
|
||||
if (promptResult.status === "failed" && isAmbiguousPromptDispatchFailure(promptResult.error)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
return true
|
||||
}
|
||||
return isInternalPromptDispatchAccepted(promptResult)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
import {
|
||||
createInternalAgentContinuationTextPart,
|
||||
isAmbiguousPromptDispatchFailure,
|
||||
isAmbiguousPostDispatchPromptFailure,
|
||||
isRealUserMessage,
|
||||
resolveInheritedPromptTools,
|
||||
} from "../../shared"
|
||||
@@ -56,7 +56,7 @@ export async function resumeSession(client: Client, config: ResumeConfig): Promi
|
||||
},
|
||||
},
|
||||
})
|
||||
if (promptResult.status === "failed" && isAmbiguousPromptDispatchFailure(promptResult.error)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
return true
|
||||
}
|
||||
return isInternalPromptDispatchAccepted(promptResult)
|
||||
|
||||
@@ -1126,7 +1126,9 @@ describe("dispatchInternalPrompt shared gate behavior", () => {
|
||||
|
||||
// then
|
||||
expect(first.status).toBe("failed")
|
||||
expect(first).toMatchObject({ dispatchAttempted: true })
|
||||
expect(second.status).toBe("failed")
|
||||
expect(second).toMatchObject({ dispatchAttempted: true })
|
||||
expect(promptCalls).toBe(2)
|
||||
})
|
||||
|
||||
@@ -1162,6 +1164,7 @@ describe("dispatchInternalPrompt shared gate behavior", () => {
|
||||
|
||||
// then
|
||||
expect(first.status).toBe("failed")
|
||||
expect(first).toMatchObject({ dispatchAttempted: true })
|
||||
expect(second).toEqual({ status: "queued", queuedBy: "test:reject:first", position: 1 })
|
||||
expect(promptCalls).toBe(1)
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../../features/claude-code-session-state"
|
||||
import {
|
||||
createInternalAgentContinuationTextPart,
|
||||
isAmbiguousPromptDispatchFailure,
|
||||
isAmbiguousPostDispatchPromptFailure,
|
||||
normalizeSDKResponse,
|
||||
resolveInheritedPromptTools,
|
||||
} from "../../shared"
|
||||
@@ -208,6 +208,15 @@ ${todoList}`
|
||||
},
|
||||
})
|
||||
if (promptResult.status === "failed") {
|
||||
if (isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
if (injectionState) {
|
||||
injectionState.inFlight = false
|
||||
injectionState.lastInjectedAt = Date.now()
|
||||
injectionState.awaitingPostInjectionProgressCheck = true
|
||||
injectionState.consecutiveFailures = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
throw promptResult.error
|
||||
}
|
||||
if (!isInternalPromptDispatchAccepted(promptResult)) {
|
||||
@@ -230,11 +239,6 @@ ${todoList}`
|
||||
if (injectionState) {
|
||||
injectionState.inFlight = false
|
||||
injectionState.lastInjectedAt = Date.now()
|
||||
if (isAmbiguousPromptDispatchFailure(error)) {
|
||||
injectionState.awaitingPostInjectionProgressCheck = true
|
||||
injectionState.consecutiveFailures = 0
|
||||
return
|
||||
}
|
||||
injectionState.consecutiveFailures = (injectionState.consecutiveFailures ?? 0) + 1
|
||||
|
||||
const errorObj = error instanceof Error
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { BackgroundManager } from "../../features/background-agent"
|
||||
import { getMainSessionID, getSessionAgent } from "../../features/claude-code-session-state"
|
||||
import { log } from "../../shared/logger"
|
||||
import { createInternalAgentTextPart, isAmbiguousPromptDispatchFailure, resolveInheritedPromptTools } from "../../shared"
|
||||
import { createInternalAgentTextPart, isAmbiguousPostDispatchPromptFailure, resolveInheritedPromptTools } from "../../shared"
|
||||
import { resolveMessageEventSessionID, resolveSessionEventID } from "../../shared/event-session-id"
|
||||
import { isAbortError } from "../../shared/is-abort-error"
|
||||
import {
|
||||
@@ -261,7 +261,7 @@ export function createUnstableAgentBabysitterHook(ctx: BabysitterContext, option
|
||||
},
|
||||
})
|
||||
if (!isInternalPromptDispatchAccepted(promptResult)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPromptDispatchFailure(promptResult.error)) {
|
||||
if (promptResult.status === "failed" && isAmbiguousPostDispatchPromptFailure(promptResult)) {
|
||||
reminderCooldowns.set(task.id, now)
|
||||
}
|
||||
log(`[${HOOK_NAME}] Reminder skipped by promptAsync gate`, {
|
||||
|
||||
Reference in New Issue
Block a user