mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 01:08:28 +00:00
fix(auth): bidirectional mode/type compat + sync OAuth to all agents (#12692)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 2dee8e1174
Co-authored-by: mudrii <220262+mudrii@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
This commit is contained in:
@@ -1,28 +1,112 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
||||
import { resolveOpenClawAgentDir } from "../agents/agent-paths.js";
|
||||
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
export { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF } from "../agents/cloudflare-ai-gateway.js";
|
||||
export { XAI_DEFAULT_MODEL_REF } from "./onboard-auth.models.js";
|
||||
|
||||
const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir();
|
||||
|
||||
export type WriteOAuthCredentialsOptions = {
|
||||
syncSiblingAgents?: boolean;
|
||||
};
|
||||
|
||||
/** Resolve real path, returning null if the target doesn't exist. */
|
||||
function safeRealpathSync(dir: string): string | null {
|
||||
try {
|
||||
return fs.realpathSync(path.resolve(dir));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSiblingAgentDirs(primaryAgentDir: string): string[] {
|
||||
const normalized = path.resolve(primaryAgentDir);
|
||||
|
||||
// Derive agentsRoot from primaryAgentDir when it matches the standard
|
||||
// layout (.../agents/<name>/agent). Falls back to global state dir.
|
||||
const parentOfAgent = path.dirname(normalized);
|
||||
const candidateAgentsRoot = path.dirname(parentOfAgent);
|
||||
const looksLikeStandardLayout =
|
||||
path.basename(normalized) === "agent" && path.basename(candidateAgentsRoot) === "agents";
|
||||
|
||||
const agentsRoot = looksLikeStandardLayout
|
||||
? candidateAgentsRoot
|
||||
: path.join(resolveStateDir(), "agents");
|
||||
|
||||
const entries = (() => {
|
||||
try {
|
||||
return fs.readdirSync(agentsRoot, { withFileTypes: true });
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
})();
|
||||
// Include both directories and symlinks-to-directories.
|
||||
const discovered = entries
|
||||
.filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
|
||||
.map((entry) => path.join(agentsRoot, entry.name, "agent"));
|
||||
|
||||
// Deduplicate via realpath to handle symlinks and path normalization.
|
||||
const seen = new Set<string>();
|
||||
const result: string[] = [];
|
||||
for (const dir of [normalized, ...discovered]) {
|
||||
const real = safeRealpathSync(dir);
|
||||
if (real && !seen.has(real)) {
|
||||
seen.add(real);
|
||||
result.push(real);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function writeOAuthCredentials(
|
||||
provider: string,
|
||||
creds: OAuthCredentials,
|
||||
agentDir?: string,
|
||||
options?: WriteOAuthCredentialsOptions,
|
||||
): Promise<string> {
|
||||
const email =
|
||||
typeof creds.email === "string" && creds.email.trim() ? creds.email.trim() : "default";
|
||||
const profileId = `${provider}:${email}`;
|
||||
const resolvedAgentDir = path.resolve(resolveAuthAgentDir(agentDir));
|
||||
const targetAgentDirs = options?.syncSiblingAgents
|
||||
? resolveSiblingAgentDirs(resolvedAgentDir)
|
||||
: [resolvedAgentDir];
|
||||
|
||||
const credential = {
|
||||
type: "oauth" as const,
|
||||
provider,
|
||||
...creds,
|
||||
};
|
||||
|
||||
// Primary write must succeed — let it throw on failure.
|
||||
upsertAuthProfile({
|
||||
profileId,
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider,
|
||||
...creds,
|
||||
},
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
credential,
|
||||
agentDir: resolvedAgentDir,
|
||||
});
|
||||
|
||||
// Sibling sync is best-effort — log and ignore individual failures.
|
||||
if (options?.syncSiblingAgents) {
|
||||
const primaryReal = safeRealpathSync(resolvedAgentDir);
|
||||
for (const targetAgentDir of targetAgentDirs) {
|
||||
const targetReal = safeRealpathSync(targetAgentDir);
|
||||
if (targetReal && primaryReal && targetReal === primaryReal) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
profileId,
|
||||
credential,
|
||||
agentDir: targetAgentDir,
|
||||
});
|
||||
} catch {
|
||||
// Best-effort: sibling sync failure must not block primary onboarding.
|
||||
}
|
||||
}
|
||||
}
|
||||
return profileId;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user