fix: name host and tool requirements in optional adapter refusals

Host and tool-configuration refusals from the pwntools, EVMole, firmware,
mitmproxy, Wakaru, LLDB and xcrun adapters carried no userMessage, so CLI
and MCP callers saw the generic "This analysis is unavailable for the
current target. Choose another analysis or target." as both message and
remediation. Choosing another target cannot satisfy a Linux-only profile
or an unset REA_*_COMMAND. The actual requirement appeared only in
details.reason.

Pass each refusal's reason as its userMessage, as the adapters' other
configuration refusals (EVMole limits, JADX, pwndbg) already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822
2026-10-09 08:12:25 +00:00
parent e6905c18e0
commit 170d0bf9e2
16 changed files with 146 additions and 35 deletions
+22 -1
View File
@@ -1,9 +1,30 @@
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
import { expect, it } from "vitest";
import { runWakaruCommand, type WakaruLauncher } from "./WakaruCommand.js";
import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import {
resolveWakaruCommand,
runWakaruCommand,
type WakaruLauncher,
} from "./WakaruCommand.js";
import { RECOVERY_LIMITS } from "./WakaruRelease.js";
it("names the Wakaru configuration requirement when it is not configured", async () => {
const refusal: unknown = await resolveWakaruCommand({}).catch(
(cause: unknown) => cause,
);
if (!(refusal instanceof AnalysisCapabilityUnavailableError))
throw new Error("Expected a capability refusal");
expect(refusal.reason).toContain("REA_WAKARU_COMMAND");
expect(projectAnalysisError(refusal)).toMatchObject({
code: "capability_unavailable",
message: refusal.reason,
remediation: { action: refusal.reason },
});
});
it("rejects an exhausted shared execution budget before launching the next command", async () => {
await expect(
runWakaruCommand({
+9 -7
View File
@@ -25,6 +25,8 @@ import {
import { RECOVERY_LIMITS } from "./WakaruRelease.js";
const OPERATION = "recover_javascript_sources";
const WAKARU_REQUIREMENT =
"Provide an absolute REA_WAKARU_COMMAND pointing to Wakaru 1.13.0 on Linux x64; no tool is installed by REA";
/** Inject the owned process launcher while retaining production protocol parsing. */
export type WakaruLauncher = (
@@ -45,7 +47,8 @@ export const resolveWakaruCommand = async (
throw new AnalysisCapabilityUnavailableError(
"wakaru",
OPERATION,
"Provide an absolute REA_WAKARU_COMMAND pointing to Wakaru 1.13.0 on Linux x64; no tool is installed by REA",
WAKARU_REQUIREMENT,
{ userMessage: WAKARU_REQUIREMENT },
);
try {
const command = await realpath(configured);
@@ -61,12 +64,11 @@ export const resolveWakaruCommand = async (
await access(limiter, constants.X_OK);
return { command, limiter, ...fingerprint };
} catch (cause: unknown) {
throw new AnalysisCapabilityUnavailableError(
"wakaru",
OPERATION,
`Configured Wakaru or util-linux prlimit is unavailable: ${recoveryFailureMessage(cause)}`,
{ cause },
);
const reason = `Configured Wakaru or util-linux prlimit is unavailable: ${recoveryFailureMessage(cause)}`;
throw new AnalysisCapabilityUnavailableError("wakaru", OPERATION, reason, {
cause,
userMessage: reason,
});
}
};
+6 -6
View File
@@ -69,12 +69,12 @@ const executeWakaruRecovery = async (context: RecoveryWorkspace) => {
if (
version.exit_code !== 0 ||
version.stdout.trim() !== `wakaru ${WAKARU_RELEASE.version}`
)
throw new AnalysisCapabilityUnavailableError(
"wakaru",
OPERATION,
`This adapter requires Wakaru ${WAKARU_RELEASE.version}; reported version: ${version.stdout.trim()}; exit: ${String(version.exit_code)}; stderr: ${version.stderr}`,
);
) {
const reason = `This adapter requires Wakaru ${WAKARU_RELEASE.version}; reported version: ${version.stdout.trim()}; exit: ${String(version.exit_code)}; stderr: ${version.stderr}`;
throw new AnalysisCapabilityUnavailableError("wakaru", OPERATION, reason, {
userMessage: reason,
});
}
const run = await runWakaruCommand({
...commandContext,
staging,