openclaw / src /auto-reply /reply /agent-runner-core.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4440aec verified
Raw History Blame Contribute Delete
8.77 kB
import { describe, expect, it, onTestFinished } from "vitest";
import { testing as cliBackendsTesting } from "../../agents/cli-backends.test-support.js";
import { resolveFallbackTransition } from "../fallback-state.js";
import type { TemplateContext } from "../templating.js";
import {
buildSilentFallbackFailurePayload,
resolveAdmittedRunSessionFile,
resolveReplyRunDeliveryContext,
} from "./agent-runner-core.js";
import type { RuntimeFallbackAttempt } from "./agent-runner-execution.types.js";
describe("resolveAdmittedRunSessionFile", () => {
it("uses the scoped session key when one is available", () => {
expect(
resolveAdmittedRunSessionFile({
agentId: "main",
sessionId: "session",
sessionFile: "legacy-target",
sessionKey: " agent:main:session ",
storePath: "/tmp/sessions.json",
}),
).toBe("agent:main:session");
});
it("preserves the admitted fallback when a persisted run has no session key", () => {
expect(
resolveAdmittedRunSessionFile({
agentId: "main",
sessionId: "session",
sessionFile: "legacy-target",
storePath: "/tmp/sessions.json",
}),
).toBe("legacy-target");
});
});
describe("resolveReplyRunDeliveryContext", () => {
it.each([
{ name: "numeric message topic", messageThreadId: 99, threadId: 99 },
{ name: "numeric transport topic", transportThreadId: 99, threadId: 99 },
{ name: "message topic precedence", messageThreadId: 99, transportThreadId: 77, threadId: 99 },
{ name: "string message topic", messageThreadId: "99", threadId: "99" },
{ name: "session identity fallback", threadId: "12345:99" },
])("preserves the $name", ({ messageThreadId, transportThreadId, threadId }) => {
expect(
resolveReplyRunDeliveryContext({
cfg: {},
sessionCtx: {
Provider: "telegram",
OriginatingChannel: "telegram",
OriginatingTo: "telegram:12345",
AccountId: "work",
MessageThreadId: messageThreadId,
TransportThreadId: transportThreadId,
SessionKey: "agent:main:telegram:direct:12345:thread:12345:99",
} as TemplateContext,
sessionKey: "agent:main:telegram:direct:12345:thread:12345:99",
}),
).toEqual({
channel: "telegram",
to: "telegram:12345",
accountId: "work",
threadId,
});
});
});
describe("buildSilentFallbackFailurePayload", () => {
const selected = { provider: "openrouter", model: "z-ai/glm-5.3" };
const other = { provider: "amazon-bedrock", model: "claude-haiku" };
const transition = resolveFallbackTransition({
selectedProvider: selected.provider,
selectedModel: selected.model,
activeProvider: other.provider,
activeModel: other.model,
attempts: [],
});
const base = {
fallbackTransition: transition,
fallbackFailureKnown: true,
cfg: {},
isHeartbeat: false,
hasSuccessfulTerminalDelivery: false,
};
const attempt = (
reason: RuntimeFallbackAttempt["reason"],
identity: Partial<RuntimeFallbackAttempt> = {},
): RuntimeFallbackAttempt => ({ ...selected, error: "provider failed", reason, ...identity });
it.each(["timeout", "server_error", "overloaded", "tls_certificate"] as const)(
"uses unreachable wording for selected-backend %s evidence",
(reason) => {
const payload = buildSilentFallbackFailurePayload({
...base,
fallbackAttempts: [attempt(reason)],
});
expect(payload?.text).toContain("couldn't reach the configured model backend");
expect(payload?.text).toContain(transition.selectedModelRef);
expect(payload?.text).toContain(transition.activeModelRef);
expect(payload?.isError).toBe(true);
},
);
it.each(["format", "empty_response"] as const)(
"uses response wording for selected-backend %s evidence",
(reason) => {
const payload = buildSilentFallbackFailurePayload({
...base,
fallbackAttempts: [attempt(reason)],
});
expect(payload?.text).toContain("responded but produced no usable reply");
expect(payload?.text).not.toContain("couldn't reach");
},
);
it.each([
{ label: "empty evidence", attempts: [] },
{ label: "unknown reason", attempts: [attempt("unknown")] },
{ label: "auth skip", attempts: [attempt("auth")] },
{ label: "auth with synthesized status", attempts: [attempt("auth", { status: 401 })] },
{ label: "permanent auth", attempts: [attempt("auth_permanent")] },
{ label: "billing", attempts: [attempt("billing")] },
{ label: "rate limit", attempts: [attempt("rate_limit")] },
...(["timeout", "overloaded", "format", "empty_response"] as const).map((reason) => ({
label: `local skip retaining ${reason}`,
attempts: [attempt(reason, { code: "MODEL_FALLBACK_SKIPPED" })],
})),
{
label: "local skip beside a response",
attempts: [attempt("format"), attempt("format", { code: "MODEL_FALLBACK_SKIPPED" })],
},
{ label: "transport then response", attempts: [attempt("timeout"), attempt("format")] },
{ label: "response then transport", attempts: [attempt("format"), attempt("timeout")] },
{ label: "other backend only", attempts: [attempt("format", other)] },
{ label: "other model only", attempts: [attempt("format", { model: "different" })] },
{ label: "unattributed response", attempts: [attempt("format", { provider: "", model: "" })] },
{
label: "unattributed transport",
attempts: [attempt("timeout", { provider: "", model: "" })],
},
{
label: "incomplete evidence beside response",
attempts: [attempt("format"), attempt("timeout", { provider: "" })],
},
{
label: "incomplete evidence beside transport",
attempts: [attempt("timeout"), attempt("format", { model: " " })],
},
])("keeps $label cause-neutral", ({ attempts }) => {
const payload = buildSilentFallbackFailurePayload({ ...base, fallbackAttempts: attempts });
expect(payload?.text).toContain("produced no usable reply");
expect(payload?.text).not.toContain("responded");
expect(payload?.text).not.toContain("couldn't reach");
});
it.each([
{
selectedReason: "format",
otherReason: "timeout",
expected: "responded but produced no usable reply",
},
{
selectedReason: "timeout",
otherReason: "format",
expected: "couldn't reach the configured model backend",
},
] as const)(
"ignores another backend's $otherReason evidence",
({ selectedReason, otherReason, expected }) => {
const payload = buildSilentFallbackFailurePayload({
...base,
fallbackAttempts: [attempt(otherReason, other), attempt(selectedReason)],
});
expect(payload?.text).toContain(expected);
},
);
it("uses the configured runtime alias when attributing a response", () => {
// Exercise attribution with setup metadata; the plugin registration test owns
// the real Claude CLI descriptor and its canonical provider binding.
onTestFinished(() => cliBackendsTesting.resetDepsForTest());
cliBackendsTesting.setDepsForTest({
resolveRuntimeCliBackends: () => [],
resolvePluginSetupCliBackend: ({ backend }) =>
backend === "claude-cli"
? {
pluginId: "anthropic",
backend: {
id: "claude-cli",
modelProvider: "anthropic",
config: { command: "claude" },
},
}
: undefined,
});
const cfg = { plugins: { allow: ["anthropic"], entries: { anthropic: { enabled: true } } } };
const fallbackTransition = resolveFallbackTransition({
selectedProvider: "anthropic",
selectedModel: "claude-sonnet-4-6",
activeProvider: "openai",
activeModel: "gpt-5.5",
attempts: [],
cfg,
});
const payload = buildSilentFallbackFailurePayload({
...base,
cfg,
fallbackTransition,
fallbackAttempts: [attempt("format", { provider: "claude-cli", model: "claude-sonnet-4-6" })],
});
expect(payload?.text).toContain("responded but produced no usable reply");
});
it.each([
{ isHeartbeat: true },
{ hasSuccessfulTerminalDelivery: true },
{ allowEmptyAssistantReplyAsSilent: true },
{ silentExpected: true },
{ hasExplicitSilentReply: true },
{ fallbackFailureKnown: false },
{ fallbackTransition: { ...transition, fallbackActive: false } },
])("preserves warning suppression %j", (suppression) => {
expect(
buildSilentFallbackFailurePayload({
...base,
...suppression,
fallbackAttempts: [attempt("format")],
}),
).toBeUndefined();
});
});