mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 03:20:22 +00:00
fix: restore claude cli guidance and doctor behavior
This commit is contained in:
@@ -479,11 +479,11 @@ export function buildAgentSystemPrompt(params: {
|
||||
|
||||
// For "none" mode, return just the basic identity line
|
||||
if (promptMode === "none") {
|
||||
return "You are a personal assistant running inside OpenClaw.";
|
||||
return "You are a personal assistant operating inside OpenClaw.";
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"You are a personal assistant running inside OpenClaw.",
|
||||
"You are a personal assistant operating inside OpenClaw.",
|
||||
"",
|
||||
"## Tooling",
|
||||
"Structured tool definitions are the source of truth for tool names, descriptions, and parameters.",
|
||||
|
||||
@@ -5,10 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { ProviderPlugin } from "../plugins/types.js";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
import {
|
||||
maybeRemoveDeprecatedCliAuthProfiles,
|
||||
maybeRepairLegacyOAuthProfileIds,
|
||||
} from "./doctor-auth.js";
|
||||
import { maybeRepairLegacyOAuthProfileIds } from "./doctor-auth.js";
|
||||
import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||
import type { DoctorRepairMode } from "./doctor-repair-mode.js";
|
||||
|
||||
@@ -58,96 +55,6 @@ afterEach(() => {
|
||||
}
|
||||
});
|
||||
|
||||
describe("maybeRemoveDeprecatedCliAuthProfiles", () => {
|
||||
it("removes deprecated CLI auth profiles from store + config", async () => {
|
||||
if (!tempAgentDir) {
|
||||
throw new Error("Missing temp agent dir");
|
||||
}
|
||||
const authPath = path.join(tempAgentDir, "auth-profiles.json");
|
||||
fs.writeFileSync(
|
||||
authPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:claude-cli": {
|
||||
type: "oauth",
|
||||
provider: "anthropic",
|
||||
access: "token-a",
|
||||
refresh: "token-r",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
"openai-codex:codex-cli": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "token-b",
|
||||
refresh: "token-r2",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
"openai-codex:default": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "token-c",
|
||||
refresh: "token-r3",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
resolvePluginProvidersMock.mockReturnValue([
|
||||
{
|
||||
id: "anthropic",
|
||||
label: "Anthropic",
|
||||
auth: [],
|
||||
deprecatedProfileIds: ["anthropic:claude-cli"],
|
||||
},
|
||||
{
|
||||
id: "openai-codex",
|
||||
label: "OpenAI Codex",
|
||||
auth: [],
|
||||
deprecatedProfileIds: ["openai-codex:codex-cli"],
|
||||
},
|
||||
]);
|
||||
|
||||
const cfg = {
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:claude-cli": { provider: "anthropic", mode: "oauth" },
|
||||
"openai-codex:codex-cli": { provider: "openai-codex", mode: "oauth" },
|
||||
"openai-codex:default": { provider: "openai-codex", mode: "oauth" },
|
||||
},
|
||||
order: {
|
||||
anthropic: ["anthropic:claude-cli"],
|
||||
"openai-codex": ["openai-codex:codex-cli", "openai-codex:default"],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const next = await maybeRemoveDeprecatedCliAuthProfiles(
|
||||
cfg as unknown as OpenClawConfig,
|
||||
makePrompter(true),
|
||||
);
|
||||
|
||||
const raw = JSON.parse(fs.readFileSync(authPath, "utf8")) as {
|
||||
profiles?: Record<string, unknown>;
|
||||
};
|
||||
expect(raw.profiles?.["anthropic:claude-cli"]).toBeUndefined();
|
||||
expect(raw.profiles?.["openai-codex:codex-cli"]).toBeUndefined();
|
||||
expect(raw.profiles?.["openai-codex:default"]).toBeDefined();
|
||||
|
||||
expect(next.auth?.profiles?.["anthropic:claude-cli"]).toBeUndefined();
|
||||
expect(next.auth?.profiles?.["openai-codex:codex-cli"]).toBeUndefined();
|
||||
expect(next.auth?.profiles?.["openai-codex:default"]).toBeDefined();
|
||||
expect(next.auth?.order?.anthropic).toBeUndefined();
|
||||
expect(next.auth?.order?.["openai-codex"]).toEqual(["openai-codex:default"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("maybeRepairLegacyOAuthProfileIds", () => {
|
||||
it("repairs provider-owned legacy OAuth profile ids", async () => {
|
||||
if (!tempAgentDir) {
|
||||
|
||||
@@ -11,16 +11,11 @@ import {
|
||||
resolveProfileUnusableUntilForDisplay,
|
||||
} from "../agents/auth-profiles.js";
|
||||
import { formatAuthDoctorHint } from "../agents/auth-profiles/doctor.js";
|
||||
import { updateAuthProfileStoreWithLock } from "../agents/auth-profiles/store.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolvePluginProviders } from "../plugins/providers.runtime.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||
import {
|
||||
buildProviderAuthRecoveryHint,
|
||||
resolveProviderAuthLoginCommand,
|
||||
} from "./provider-auth-guidance.js";
|
||||
import { buildProviderAuthRecoveryHint } from "./provider-auth-guidance.js";
|
||||
|
||||
export async function maybeRepairLegacyOAuthProfileIds(
|
||||
cfg: OpenClawConfig,
|
||||
@@ -59,167 +54,6 @@ export async function maybeRepairLegacyOAuthProfileIds(
|
||||
return nextCfg;
|
||||
}
|
||||
|
||||
function pruneAuthOrder(
|
||||
order: Record<string, string[]> | undefined,
|
||||
profileIds: Set<string>,
|
||||
): { next: Record<string, string[]> | undefined; changed: boolean } {
|
||||
if (!order) {
|
||||
return { next: order, changed: false };
|
||||
}
|
||||
let changed = false;
|
||||
const next: Record<string, string[]> = {};
|
||||
for (const [provider, list] of Object.entries(order)) {
|
||||
const filtered = list.filter((id) => !profileIds.has(id));
|
||||
if (filtered.length !== list.length) {
|
||||
changed = true;
|
||||
}
|
||||
if (filtered.length > 0) {
|
||||
next[provider] = filtered;
|
||||
}
|
||||
}
|
||||
return { next: Object.keys(next).length > 0 ? next : undefined, changed };
|
||||
}
|
||||
|
||||
function pruneAuthProfiles(
|
||||
cfg: OpenClawConfig,
|
||||
profileIds: Set<string>,
|
||||
): { next: OpenClawConfig; changed: boolean } {
|
||||
const profiles = cfg.auth?.profiles;
|
||||
const order = cfg.auth?.order;
|
||||
const nextProfiles = profiles ? { ...profiles } : undefined;
|
||||
let changed = false;
|
||||
|
||||
if (nextProfiles) {
|
||||
for (const id of profileIds) {
|
||||
if (id in nextProfiles) {
|
||||
delete nextProfiles[id];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prunedOrder = pruneAuthOrder(order, profileIds);
|
||||
if (prunedOrder.changed) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return { next: cfg, changed: false };
|
||||
}
|
||||
|
||||
const nextAuth =
|
||||
nextProfiles || prunedOrder.next
|
||||
? {
|
||||
...cfg.auth,
|
||||
profiles: nextProfiles && Object.keys(nextProfiles).length > 0 ? nextProfiles : undefined,
|
||||
order: prunedOrder.next,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
next: {
|
||||
...cfg,
|
||||
auth: nextAuth,
|
||||
},
|
||||
changed: true,
|
||||
};
|
||||
}
|
||||
|
||||
export async function maybeRemoveDeprecatedCliAuthProfiles(
|
||||
cfg: OpenClawConfig,
|
||||
prompter: DoctorPrompter,
|
||||
): Promise<OpenClawConfig> {
|
||||
const store = ensureAuthProfileStore(undefined, { allowKeychainPrompt: false });
|
||||
const providers = resolvePluginProviders({
|
||||
config: cfg,
|
||||
env: process.env,
|
||||
mode: "setup",
|
||||
});
|
||||
const deprecatedEntries = providers.flatMap((provider) =>
|
||||
(provider.deprecatedProfileIds ?? [])
|
||||
.filter((profileId) => store.profiles[profileId] || cfg.auth?.profiles?.[profileId])
|
||||
.map((profileId) => ({
|
||||
profileId,
|
||||
providerId: provider.id,
|
||||
providerLabel: provider.label,
|
||||
})),
|
||||
);
|
||||
const deprecated = new Set(deprecatedEntries.map((entry) => entry.profileId));
|
||||
|
||||
if (deprecated.size === 0) {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
const lines = ["Deprecated external CLI auth profiles detected (no longer supported):"];
|
||||
for (const entry of deprecatedEntries) {
|
||||
const authCommand =
|
||||
resolveProviderAuthLoginCommand({
|
||||
provider: entry.providerId,
|
||||
config: cfg,
|
||||
env: process.env,
|
||||
}) ?? formatCliCommand("openclaw configure");
|
||||
lines.push(`- ${entry.profileId} (${entry.providerLabel}): use ${authCommand}`);
|
||||
}
|
||||
note(lines.join("\n"), "Auth profiles");
|
||||
|
||||
const shouldRemove = await prompter.confirmAutoFix({
|
||||
message: "Remove deprecated CLI auth profiles now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (!shouldRemove) {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
await updateAuthProfileStoreWithLock({
|
||||
updater: (nextStore) => {
|
||||
let mutated = false;
|
||||
for (const id of deprecated) {
|
||||
if (nextStore.profiles[id]) {
|
||||
delete nextStore.profiles[id];
|
||||
mutated = true;
|
||||
}
|
||||
if (nextStore.usageStats?.[id]) {
|
||||
delete nextStore.usageStats[id];
|
||||
mutated = true;
|
||||
}
|
||||
}
|
||||
if (nextStore.order) {
|
||||
for (const [provider, list] of Object.entries(nextStore.order)) {
|
||||
const filtered = list.filter((id) => !deprecated.has(id));
|
||||
if (filtered.length !== list.length) {
|
||||
mutated = true;
|
||||
if (filtered.length > 0) {
|
||||
nextStore.order[provider] = filtered;
|
||||
} else {
|
||||
delete nextStore.order[provider];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextStore.lastGood) {
|
||||
for (const [provider, profileId] of Object.entries(nextStore.lastGood)) {
|
||||
if (deprecated.has(profileId)) {
|
||||
delete nextStore.lastGood[provider];
|
||||
mutated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mutated;
|
||||
},
|
||||
});
|
||||
|
||||
const pruned = pruneAuthProfiles(cfg, deprecated);
|
||||
if (pruned.changed) {
|
||||
note(
|
||||
Array.from(deprecated.values())
|
||||
.map((id) => `- removed ${id} from config`)
|
||||
.join("\n"),
|
||||
"Doctor changes",
|
||||
);
|
||||
}
|
||||
return pruned.next;
|
||||
}
|
||||
|
||||
type AuthIssue = {
|
||||
profileId: string;
|
||||
provider: string;
|
||||
|
||||
@@ -598,7 +598,7 @@ describe("modelsAuthLoginCommand", () => {
|
||||
"Anthropic setup-token auth is a legacy/manual path in OpenClaw.",
|
||||
);
|
||||
expect(runtime.log).toHaveBeenCalledWith(
|
||||
"Anthropic told OpenClaw users this path requires Extra Usage on the Claude account.",
|
||||
"Anthropic staff told us this OpenClaw path is allowed again.",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -421,9 +421,7 @@ export async function modelsAuthPasteTokenCommand(
|
||||
runtime.log(`Auth profile: ${profileId} (${provider}/token)`);
|
||||
if (provider === "anthropic") {
|
||||
runtime.log("Anthropic setup-token auth is a legacy/manual path in OpenClaw.");
|
||||
runtime.log(
|
||||
"Anthropic told OpenClaw users this path requires Extra Usage on the Claude account.",
|
||||
);
|
||||
runtime.log("Anthropic staff told us this OpenClaw path is allowed again.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { runChannelPluginStartupMaintenance } from "../channels/plugins/lifecycle-startup.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import {
|
||||
maybeRemoveDeprecatedCliAuthProfiles,
|
||||
maybeRepairLegacyOAuthProfileIds,
|
||||
noteAuthProfileHealth,
|
||||
} from "../commands/doctor-auth.js";
|
||||
@@ -143,7 +142,6 @@ async function runGatewayConfigHealth(ctx: DoctorHealthFlowContext): Promise<voi
|
||||
|
||||
async function runAuthProfileHealth(ctx: DoctorHealthFlowContext): Promise<void> {
|
||||
ctx.cfg = await maybeRepairLegacyOAuthProfileIds(ctx.cfg, ctx.prompter);
|
||||
ctx.cfg = await maybeRemoveDeprecatedCliAuthProfiles(ctx.cfg, ctx.prompter);
|
||||
await noteAuthProfileHealth({
|
||||
cfg: ctx.cfg,
|
||||
prompter: ctx.prompter,
|
||||
|
||||
Reference in New Issue
Block a user