mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-05-01 06:36:23 +08:00
fix: isolate active memory auth health (#71539)
* fix(agents): scope helper auth failures * fix(active-memory): isolate recall auth health * fix: isolate active memory auth health (#71539) * fix: avoid auth policy import cycle (#71539)
This commit is contained in:
@@ -70,6 +70,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/tool-result pruning: harden the tool-result character estimator and context-pruning loops against malformed `{ type: "text" }` blocks created by void or undefined tool handler results, serializing non-string text payloads for size accounting so they cannot bypass trimming as zero-sized. Fixes #34979. (#51267) Thanks @cgdusek, @alvinttang, and @coffeexcoin.
|
||||
- Daemon/service-env: add Nix Home Manager profile bin directories to generated gateway service PATHs on macOS and Linux, honoring `NIX_PROFILES` right-to-left precedence and falling back to `~/.nix-profile/bin` when unset. Fixes #44402. (#59935) Thanks @jerome-benoit.
|
||||
- Agents/heartbeat: stop injecting the heartbeat system prompt into non-heartbeat runs, preventing ordinary user replies from being suppressed as `HEARTBEAT_OK` acknowledgments. Fixes #69079. (#69278) Thanks @stainlu.
|
||||
- Active Memory: keep silent recall sub-agent billing/auth failures out of shared auth-profile cooldown state, so a Claude CLI extra-usage rejection cannot disable normal Claude-backed turns. Fixes #71284. (#71539) Thanks @vishutdhar and @obviyus.
|
||||
|
||||
## 2026.4.25 (Unreleased)
|
||||
|
||||
|
||||
@@ -162,6 +162,24 @@ describe("active-memory plugin", () => {
|
||||
expect(api.on).toHaveBeenCalledWith("before_prompt_build", expect.any(Function));
|
||||
});
|
||||
|
||||
it("runs recall without recording shared auth-profile failures", async () => {
|
||||
await hooks.before_prompt_build(
|
||||
{ prompt: "what wings should i order?", messages: [] },
|
||||
{
|
||||
agentId: "main",
|
||||
trigger: "user",
|
||||
sessionKey: "agent:main:main",
|
||||
messageProvider: "webchat",
|
||||
},
|
||||
);
|
||||
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
authProfileFailurePolicy: "local",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("registers a session-scoped active-memory toggle command", async () => {
|
||||
const command = registeredCommands["active-memory"];
|
||||
const sessionKey = "agent:main:active-memory-toggle";
|
||||
|
||||
@@ -1685,6 +1685,7 @@ async function runRecallSubagent(params: {
|
||||
thinkLevel: params.config.thinking,
|
||||
reasoningLevel: "off",
|
||||
silentExpected: true,
|
||||
authProfileFailurePolicy: "local",
|
||||
cleanupBundleMcpOnRunEnd: true,
|
||||
abortSignal: params.abortSignal,
|
||||
});
|
||||
|
||||
@@ -90,6 +90,7 @@ import { resolveModelAsync } from "./model.js";
|
||||
import { createEmbeddedRunReplayState, observeReplayMetadata } from "./replay-state.js";
|
||||
import { handleAssistantFailover } from "./run/assistant-failover.js";
|
||||
import { createEmbeddedRunAuthController } from "./run/auth-controller.js";
|
||||
import { resolveAuthProfileFailureReason } from "./run/auth-profile-failure-policy.js";
|
||||
import { runEmbeddedAttemptWithBackend } from "./run/backend.js";
|
||||
import { createFailoverDecisionLogger } from "./run/failover-observation.js";
|
||||
import { mergeRetryFailoverReason, resolveRunFailoverDecision } from "./run/failover-policy.js";
|
||||
@@ -669,16 +670,11 @@ export async function runEmbeddedPiAgent(
|
||||
modelId: failure.modelId,
|
||||
});
|
||||
};
|
||||
const resolveAuthProfileFailureReason = (
|
||||
failoverReason: FailoverReason | null,
|
||||
): AuthProfileFailureReason | null => {
|
||||
// Timeouts are transport/model-path failures, not auth health signals,
|
||||
// so they should not persist auth-profile failure state.
|
||||
if (!failoverReason || failoverReason === "timeout") {
|
||||
return null;
|
||||
}
|
||||
return failoverReason;
|
||||
};
|
||||
const resolveRunAuthProfileFailureReason = (failoverReason: FailoverReason | null) =>
|
||||
resolveAuthProfileFailureReason({
|
||||
failoverReason,
|
||||
policy: params.authProfileFailurePolicy,
|
||||
});
|
||||
const maybeBackoffBeforeOverloadFailover = async (reason: FailoverReason | null) => {
|
||||
if (reason !== "overloaded" || overloadFailoverBackoffMs <= 0) {
|
||||
return;
|
||||
@@ -1485,7 +1481,7 @@ export async function runEmbeddedPiAgent(
|
||||
const promptFailoverReason =
|
||||
promptErrorDetails.reason ?? classifyFailoverReason(errorText, { provider });
|
||||
const promptProfileFailureReason =
|
||||
resolveAuthProfileFailureReason(promptFailoverReason);
|
||||
resolveRunAuthProfileFailureReason(promptFailoverReason);
|
||||
await maybeMarkAuthProfileFailure({
|
||||
profileId: lastProfileId,
|
||||
reason: promptProfileFailureReason,
|
||||
@@ -1630,7 +1626,7 @@ export async function runEmbeddedPiAgent(
|
||||
},
|
||||
);
|
||||
const assistantProfileFailureReason =
|
||||
resolveAuthProfileFailureReason(assistantFailoverReason);
|
||||
resolveRunAuthProfileFailureReason(assistantFailoverReason);
|
||||
const cloudCodeAssistFormatError = attempt.cloudCodeAssistFormatError;
|
||||
const imageDimensionError = parseImageDimensionError(
|
||||
assistantForFailover?.errorMessage ?? "",
|
||||
@@ -2047,7 +2043,7 @@ export async function runEmbeddedPiAgent(
|
||||
if (lastProfileId) {
|
||||
await maybeMarkAuthProfileFailure({
|
||||
profileId: lastProfileId,
|
||||
reason: resolveAuthProfileFailureReason(assistantFailoverReason),
|
||||
reason: resolveRunAuthProfileFailureReason(assistantFailoverReason),
|
||||
});
|
||||
}
|
||||
return {
|
||||
@@ -2157,7 +2153,7 @@ export async function runEmbeddedPiAgent(
|
||||
if (lastProfileId) {
|
||||
await maybeMarkAuthProfileFailure({
|
||||
profileId: lastProfileId,
|
||||
reason: resolveAuthProfileFailureReason(assistantFailoverReason),
|
||||
reason: resolveRunAuthProfileFailureReason(assistantFailoverReason),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAuthProfileFailureReason } from "./auth-profile-failure-policy.js";
|
||||
|
||||
describe("resolveAuthProfileFailureReason", () => {
|
||||
it("records shared non-timeout provider failures", () => {
|
||||
expect(
|
||||
resolveAuthProfileFailureReason({
|
||||
failoverReason: "billing",
|
||||
policy: "shared",
|
||||
}),
|
||||
).toBe("billing");
|
||||
expect(
|
||||
resolveAuthProfileFailureReason({
|
||||
failoverReason: "rate_limit",
|
||||
policy: "shared",
|
||||
}),
|
||||
).toBe("rate_limit");
|
||||
});
|
||||
|
||||
it("does not record local helper failures in shared auth state", () => {
|
||||
expect(
|
||||
resolveAuthProfileFailureReason({
|
||||
failoverReason: "billing",
|
||||
policy: "local",
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
resolveAuthProfileFailureReason({
|
||||
failoverReason: "auth",
|
||||
policy: "local",
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("does not persist transport timeouts as auth-profile health", () => {
|
||||
expect(
|
||||
resolveAuthProfileFailureReason({
|
||||
failoverReason: "timeout",
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { AuthProfileFailureReason } from "../../auth-profiles/types.js";
|
||||
import type { FailoverReason } from "../../pi-embedded-helpers/types.js";
|
||||
import type { AuthProfileFailurePolicy } from "./auth-profile-failure-policy.types.js";
|
||||
|
||||
export function resolveAuthProfileFailureReason(params: {
|
||||
failoverReason: FailoverReason | null;
|
||||
policy?: AuthProfileFailurePolicy;
|
||||
}): AuthProfileFailureReason | null {
|
||||
// Helper-local runs and transport timeouts should not poison shared provider auth health.
|
||||
if (params.policy === "local" || !params.failoverReason || params.failoverReason === "timeout") {
|
||||
return null;
|
||||
}
|
||||
return params.failoverReason;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export type AuthProfileFailurePolicy = "shared" | "local";
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
ToolResultFormat,
|
||||
} from "../../pi-embedded-subscribe.shared-types.js";
|
||||
import type { SkillSnapshot } from "../../skills.js";
|
||||
import type { AuthProfileFailurePolicy } from "./auth-profile-failure-policy.types.js";
|
||||
export type { ClientToolDefinition } from "../../command/shared-types.js";
|
||||
|
||||
export type EmbeddedRunTrigger = "cron" | "heartbeat" | "manual" | "memory" | "overflow" | "user";
|
||||
@@ -139,6 +140,7 @@ export type RunEmbeddedPiAgentParams = {
|
||||
ownerNumbers?: string[];
|
||||
enforceFinalTag?: boolean;
|
||||
silentExpected?: boolean;
|
||||
authProfileFailurePolicy?: AuthProfileFailurePolicy;
|
||||
/**
|
||||
* Allow a single run attempt even when all auth profiles are in cooldown,
|
||||
* but only for inferred transient cooldowns like `rate_limit` or `overloaded`.
|
||||
|
||||
Reference in New Issue
Block a user