fix(doctor): import default-agent auth profiles into sqlite (#93156)

* fix(doctor): import default-agent auth profiles into sqlite

* fix(doctor): type legacy config auth imports

* fix(doctor): preserve auth json precedence

* fix(doctor): import config auth with state-only stores

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
This commit is contained in:
pick-cat 2026-06-16 05:42:51 +08:00 committed by GitHub
parent 8b0eac7927
commit f018945eff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 721 additions and 9 deletions

View file

@ -442,6 +442,413 @@ describe("maybeMigrateAuthProfileJsonStoresToSqlite", () => {
expect(fs.existsSync(authPath)).toBe(true);
expect(fs.existsSync(`${authPath}.sqlite-import.464.bak`)).toBe(false);
});
it("imports default-agent config auth profiles into sqlite when no legacy files exist", async () => {
const state = await makeTestState();
const cfg = {
auth: {
profiles: {
"openai:default": {
provider: "openai",
mode: "api_key",
key: "sk-config",
},
"anthropic:default": {
provider: "anthropic",
mode: "token",
token: {
source: "env",
provider: "default",
id: "ANTHROPIC_TOKEN",
},
},
"router:default": {
provider: "router",
mode: "api_key",
displayName: "routing only",
},
},
order: {
openai: ["openai:default"],
anthropic: ["anthropic:default"],
},
},
} as OpenClawConfig;
const result = await maybeMigrateAuthProfileJsonStoresToSqlite({
cfg,
prompter: makePrompter(true),
now: () => 465,
});
const authPath = `${state.agentDir()}/auth-profiles.json`;
expect(result.detected).toEqual([authPath]);
expect(result.configChanged).toBe(true);
expect(result.warnings).toStrictEqual([]);
expect(cfg.auth?.profiles?.["openai:default"]).toEqual({
provider: "openai",
mode: "api_key",
});
expect(cfg.auth?.profiles?.["anthropic:default"]).toEqual({
provider: "anthropic",
mode: "token",
});
expect(cfg.auth?.profiles?.["router:default"]).toEqual({
provider: "router",
mode: "api_key",
displayName: "routing only",
});
expect(cfg.auth?.order).toEqual({
openai: ["openai:default"],
anthropic: ["anthropic:default"],
});
expect(loadPersistedAuthProfileStore(state.agentDir())).toMatchObject({
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
key: "sk-config",
},
"anthropic:default": {
type: "token",
provider: "anthropic",
tokenRef: {
source: "env",
provider: "default",
id: "ANTHROPIC_TOKEN",
},
},
},
});
expect(
loadPersistedAuthProfileStore(state.agentDir())?.profiles["router:default"],
).toBeUndefined();
expect(loadPersistedAuthProfileStore(state.agentDir())?.order).toBeUndefined();
expect(fs.existsSync(authPath)).toBe(false);
expect(fs.existsSync(`${authPath}.sqlite-import.465.bak`)).toBe(false);
});
it("imports default-agent config auth profiles when only legacy state exists", async () => {
const state = await makeTestState();
const statePath = await state.writeText(
"agents/main/agent/auth-state.json",
`${JSON.stringify({
version: 1,
order: { openai: ["openai:default"] },
lastGood: { openai: "openai:default" },
})}\n`,
);
const cfg = {
auth: {
profiles: {
"openai:default": {
provider: "openai",
mode: "api_key",
key: "sk-config",
},
},
},
} as OpenClawConfig;
const result = await maybeMigrateAuthProfileJsonStoresToSqlite({
cfg,
prompter: makePrompter(true),
now: () => 467,
});
const authPath = `${state.agentDir()}/auth-profiles.json`;
expect(result.detected.toSorted()).toEqual([authPath, statePath].toSorted());
expect(result.configChanged).toBe(true);
expect(result.warnings).toStrictEqual([]);
expect(cfg.auth?.profiles?.["openai:default"]).toEqual({
provider: "openai",
mode: "api_key",
});
expect(loadPersistedAuthProfileStore(state.agentDir())).toMatchObject({
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
key: "sk-config",
},
},
order: { openai: ["openai:default"] },
lastGood: { openai: "openai:default" },
});
expect(fs.existsSync(statePath)).toBe(false);
expect(fs.existsSync(`${statePath}.sqlite-import.467.bak`)).toBe(true);
expect(fs.existsSync(authPath)).toBe(false);
expect(fs.existsSync(`${authPath}.sqlite-import.467.bak`)).toBe(false);
});
it("infers config credential provider and mode before stripping config", async () => {
const cases: Array<{ profileId: string; cfg: OpenClawConfig; now: number }> = [
{
profileId: "openai:default",
cfg: {
auth: { profiles: { "openai:default": { key: "sk-config" } } },
} as unknown as OpenClawConfig,
now: 468,
},
{
profileId: "work",
cfg: {
auth: { profiles: { work: { key: "sk-config" } } },
agents: { defaults: { model: { primary: "openai/gpt-5.5@work" } } },
} as unknown as OpenClawConfig,
now: 470,
},
{
profileId: "ordered",
cfg: {
auth: {
profiles: { ordered: { key: "sk-config" } },
order: { openai: ["ordered"] },
},
} as unknown as OpenClawConfig,
now: 474,
},
];
for (const entry of cases) {
const state = await makeTestState();
const result = await maybeMigrateAuthProfileJsonStoresToSqlite({
cfg: entry.cfg,
prompter: makePrompter(true),
now: () => entry.now,
});
const authPath = `${state.agentDir()}/auth-profiles.json`;
expect(result.detected).toEqual([authPath]);
expect(result.configChanged).toBe(true);
expect(result.warnings).toStrictEqual([]);
expect(entry.cfg.auth?.profiles?.[entry.profileId]).toEqual({
provider: "openai",
mode: "api_key",
});
expect(loadPersistedAuthProfileStore(state.agentDir())?.profiles[entry.profileId]).toEqual({
type: "api_key",
provider: "openai",
key: "sk-config",
});
expect(fs.existsSync(authPath)).toBe(false);
expect(fs.existsSync(`${authPath}.sqlite-import.${entry.now}.bak`)).toBe(false);
}
});
it("imports missing config credentials while preserving legacy JSON precedence", async () => {
const state = await makeTestState();
const authPath = await writeLegacyAuthProfilesJson(state, {
version: 1,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
key: "sk-json",
},
"openai:work": {
type: "api_key",
provider: "openai",
key: "sk-work",
},
},
order: {
openai: ["openai:work"],
},
});
const cfg = {
auth: {
profiles: {
"openai:default": {
provider: "openai",
mode: "api_key",
key: "sk-config",
},
"anthropic:default": {
provider: "anthropic",
mode: "api_key",
key: "sk-anthropic",
},
},
order: {
openai: ["openai:default"],
},
},
} as OpenClawConfig;
const result = await maybeMigrateAuthProfileJsonStoresToSqlite({
cfg,
prompter: makePrompter(true),
now: () => 471,
});
expect(result.detected).toEqual([authPath]);
expect(result.configChanged).toBe(true);
expect(result.warnings).toStrictEqual([]);
expect(loadPersistedAuthProfileStore(state.agentDir())?.profiles).toMatchObject({
"openai:default": {
type: "api_key",
provider: "openai",
key: "sk-json",
},
"openai:work": {
type: "api_key",
provider: "openai",
key: "sk-work",
},
"anthropic:default": {
type: "api_key",
provider: "anthropic",
key: "sk-anthropic",
},
});
expect(cfg.auth?.profiles?.["openai:default"]).toEqual({
provider: "openai",
mode: "api_key",
});
expect(cfg.auth?.profiles?.["anthropic:default"]).toEqual({
provider: "anthropic",
mode: "api_key",
});
expect(loadPersistedAuthProfileStore(state.agentDir())).toMatchObject({
order: {
openai: ["openai:work"],
},
});
expect(fs.existsSync(authPath)).toBe(false);
expect(fs.existsSync(`${authPath}.sqlite-import.471.bak`)).toBe(true);
});
it("imports default-agent config api key alias SecretRefs as key refs", async () => {
const cases = [
{
profileId: "openai:api-key-object",
profile: {
provider: "openai",
apiKey: {
source: "env",
provider: "default",
id: "OPENAI_API_KEY",
},
},
},
{
profileId: "openai:api-key-template",
profile: {
provider: "openai",
mode: "api_key",
apiKey: "${OPENAI_API_KEY}",
},
},
{
profileId: "openai:api-key-legacy-field",
profile: {
provider: "openai",
api_key: {
source: "env",
provider: "default",
id: "OPENAI_API_KEY",
},
},
},
];
for (const entry of cases) {
const state = await makeTestState();
const cfg = {
auth: {
profiles: {
[entry.profileId]: entry.profile,
},
},
} as unknown as OpenClawConfig;
const result = await maybeMigrateAuthProfileJsonStoresToSqlite({
cfg,
prompter: makePrompter(true),
now: () => 473,
});
expect(result.configChanged).toBe(true);
expect(result.warnings).toStrictEqual([]);
expect(cfg.auth?.profiles?.[entry.profileId]).toEqual({
provider: "openai",
mode: "api_key",
});
expect(loadPersistedAuthProfileStore(state.agentDir())?.profiles[entry.profileId]).toEqual({
type: "api_key",
provider: "openai",
keyRef: {
source: "env",
provider: "default",
id: "OPENAI_API_KEY",
},
});
}
});
it("uses config credentials only when same-id sqlite credentials are incomplete", async () => {
const cases = [
{
existing: {
type: "api_key" as const,
provider: "openai",
key: "sk-sqlite",
},
expectedKey: "sk-sqlite",
},
{
existing: {
type: "api_key" as const,
provider: "openai",
},
expectedKey: "sk-config",
},
];
for (const entry of cases) {
const state = await makeTestState();
saveAuthProfileStore(
{
version: 1,
profiles: {
"openai:default": entry.existing,
},
},
state.agentDir(),
{ syncExternalCli: false },
);
const cfg = {
auth: {
profiles: {
"openai:default": {
provider: "openai",
mode: "api_key",
key: "sk-config",
},
},
},
} as OpenClawConfig;
const result = await maybeMigrateAuthProfileJsonStoresToSqlite({
cfg,
prompter: makePrompter(true),
now: () => 469,
});
expect(result.configChanged).toBe(true);
expect(result.warnings).toStrictEqual([]);
expect(cfg.auth?.profiles?.["openai:default"]).toEqual({
provider: "openai",
mode: "api_key",
});
expect(loadPersistedAuthProfileStore(state.agentDir())?.profiles["openai:default"]).toEqual({
type: "api_key",
provider: "openai",
key: entry.expectedKey,
});
}
});
});
describe("maybeRepairLegacyFlatAuthProfileStores", () => {

View file

@ -1,6 +1,7 @@
/** Doctor repairs for legacy auth profile JSON stores and OpenAI provider-id migrations. */
import fs from "node:fs";
import path from "node:path";
import { collectConfiguredModelRefs } from "@openclaw/model-catalog-core/configured-model-refs";
import { isRecord } from "@openclaw/normalization-core/record-coerce";
import { note } from "../../packages/terminal-core/src/note.js";
import { resolveAgentDir, resolveDefaultAgentDir, listAgentIds } from "../agents/agent-scope.js";
@ -26,6 +27,7 @@ import type {
AuthProfileState,
AuthProfileStore,
} from "../agents/auth-profiles/types.js";
import { splitTrailingAuthProfile } from "../agents/model-ref-profile.js";
import { formatCliCommand } from "../cli/command-format.js";
import { resolveStateDir } from "../config/paths.js";
import type { AuthProfileConfig } from "../config/types.auth.js";
@ -65,6 +67,12 @@ type AwsSdkAuthProfileMarkerStore = {
profiles: AwsSdkProfileMarker[];
};
type RawAuthProfileImportStore = {
version: number;
profiles: Record<string, Record<string, unknown>>;
order?: Record<string, string[]>;
};
export type LegacyFlatAuthProfileRepairResult = {
detected: string[];
changes: string[];
@ -90,6 +98,86 @@ function extractProviderFromProfileId(profileId: string): string | undefined {
return readNonEmptyString(profileId.slice(0, colon));
}
function extractProviderFromModelRef(modelRef: string): string | undefined {
const { model } = splitTrailingAuthProfile(modelRef);
const slash = model.indexOf("/");
if (slash <= 0) {
return undefined;
}
return readNonEmptyString(model.slice(0, slash));
}
function collectLegacyConfigAuthProfileProviderHints(
cfg: OpenClawConfig,
): ReadonlyMap<string, string> {
const hints = new Map<string, string>();
const conflicted = new Set<string>();
const addHint = (profileId: string, provider: string): void => {
const existing = hints.get(profileId);
if (existing && existing !== provider) {
hints.delete(profileId);
conflicted.add(profileId);
return;
}
if (!conflicted.has(profileId)) {
hints.set(profileId, provider);
}
};
const addModelHints = (models: unknown): void => {
if (!isRecord(models)) {
return;
}
for (const [modelRef, rawModel] of Object.entries(models)) {
const provider = extractProviderFromModelRef(modelRef);
if (!provider || !isSafeLegacyProviderKey(provider) || !isRecord(rawModel)) {
continue;
}
const agentRuntime = isRecord(rawModel.agentRuntime) ? rawModel.agentRuntime : null;
const authProfileId = agentRuntime
? readNonEmptyString(agentRuntime.authProfileId)
: undefined;
if (authProfileId) {
addHint(authProfileId, provider);
}
}
};
for (const { value } of collectConfiguredModelRefs(cfg)) {
const { profile } = splitTrailingAuthProfile(value);
const provider = extractProviderFromModelRef(value);
if (profile && provider && isSafeLegacyProviderKey(provider)) {
addHint(profile, provider);
}
}
const root: Record<string, unknown> = cfg;
const auth = isRecord(root.auth) ? root.auth : null;
const order = auth && isRecord(auth.order) ? auth.order : null;
if (order) {
for (const [provider, profileIds] of Object.entries(order)) {
if (!isSafeLegacyProviderKey(provider) || !Array.isArray(profileIds)) {
continue;
}
for (const profileId of profileIds) {
const normalizedProfileId = readNonEmptyString(profileId);
if (normalizedProfileId) {
addHint(normalizedProfileId, provider);
}
}
}
}
const agents = isRecord(root.agents) ? root.agents : null;
const defaults = agents && isRecord(agents.defaults) ? agents.defaults : null;
addModelHints(defaults?.models);
const agentList = agents && Array.isArray(agents.list) ? agents.list : [];
for (const agent of agentList) {
if (isRecord(agent)) {
addModelHints(agent.models);
}
}
return hints;
}
function inferLegacyCredentialType(
record: Record<string, unknown>,
): AuthProfileCredential["type"] | undefined {
@ -293,15 +381,177 @@ function collectAuthProfileStateProfileIds(state: AuthProfileState): string[] {
return [...profileIds];
}
function inferLegacyConfigAuthProfileMode(
raw: Record<string, unknown>,
): AuthProfileCredential["type"] | undefined {
const explicit = readNonEmptyString(raw.mode) ?? readNonEmptyString(raw.type);
if (explicit === "api_key" || explicit === "token" || explicit === "oauth") {
return explicit;
}
if (
readNonEmptyString(raw.key) ||
readNonEmptyString(raw.apiKey) ||
readNonEmptyString(raw["api_key"]) ||
coerceSecretRef(raw.keyRef) ||
coerceSecretRef(raw.key) ||
coerceSecretRef(raw.apiKey) ||
coerceSecretRef(raw["api_key"])
) {
return "api_key";
}
if (
readNonEmptyString(raw.token) ||
coerceSecretRef(raw.tokenRef) ||
coerceSecretRef(raw.token)
) {
return "token";
}
if (
readNonEmptyString(raw.access) &&
readNonEmptyString(raw.refresh) &&
typeof raw.expires === "number"
) {
return "oauth";
}
return undefined;
}
function coerceLegacyConfigAuthProfileStore(cfg: OpenClawConfig): AuthProfileStore | null {
const cfgRecord: Record<string, unknown> = cfg;
const auth = isRecord(cfgRecord.auth) ? cfgRecord.auth : null;
const profiles = auth && isRecord(auth.profiles) ? auth.profiles : null;
if (!profiles) {
return null;
}
const providerHints = collectLegacyConfigAuthProfileProviderHints(cfg);
const store: RawAuthProfileImportStore = { version: AUTH_STORE_VERSION, profiles: {} };
for (const [profileId, raw] of Object.entries(profiles)) {
if (!isRecord(raw)) {
continue;
}
const mode = inferLegacyConfigAuthProfileMode(raw);
if (mode !== "api_key" && mode !== "token" && mode !== "oauth") {
continue;
}
const provider =
readNonEmptyString(raw.provider) ??
extractProviderFromProfileId(profileId) ??
providerHints.get(profileId);
if (!provider || !isSafeLegacyProviderKey(provider)) {
continue;
}
const next: Record<string, unknown> = { ...raw, provider, mode };
if (mode === "api_key") {
const keyRef =
coerceSecretRef(raw.keyRef) ??
coerceSecretRef(raw.key) ??
coerceSecretRef(raw.apiKey) ??
coerceSecretRef(raw["api_key"]);
const key =
readNonEmptyString(raw.key) ??
readNonEmptyString(raw.apiKey) ??
readNonEmptyString(raw["api_key"]);
if (keyRef) {
next.keyRef = keyRef;
delete next.key;
delete next.apiKey;
delete next["api_key"];
} else if (key) {
next.key = key;
delete next.keyRef;
} else {
continue;
}
} else if (mode === "token") {
const tokenRef = coerceSecretRef(raw.tokenRef) ?? coerceSecretRef(raw.token);
const token = readNonEmptyString(raw.token);
if (tokenRef) {
next.tokenRef = tokenRef;
delete next.token;
} else if (token) {
next.token = token;
delete next.tokenRef;
} else {
continue;
}
} else if (
!readNonEmptyString(raw.access) ||
!readNonEmptyString(raw.refresh) ||
typeof raw.expires !== "number"
) {
continue;
}
store.profiles[profileId] = next;
}
const canonicalStore = coercePersistedAuthProfileStore(store);
return canonicalStore && Object.keys(canonicalStore.profiles).length > 0 ? canonicalStore : null;
}
function isDefaultAgentCandidate(
candidate: AuthProfileSqliteMigrationCandidate,
cfg: OpenClawConfig,
env: NodeJS.ProcessEnv,
): boolean {
return path.resolve(candidate.agentDir ?? "") === path.resolve(resolveDefaultAgentDir(cfg, env));
}
function stripImportedConfigAuthProfileCredentials(
cfg: OpenClawConfig,
store: AuthProfileStore,
): boolean {
const profiles = ensureConfigAuthProfiles(cfg);
let changed = false;
for (const [profileId, credential] of Object.entries(store.profiles)) {
const current = profiles[profileId];
if (!current) {
continue;
}
const metadata: AuthProfileConfig = {
provider: current.provider || credential.provider,
mode: credential.type,
...(current.email ? { email: current.email } : {}),
...(current.displayName ? { displayName: current.displayName } : {}),
};
profiles[profileId] = metadata;
changed = true;
}
return changed;
}
function hasUsableAuthProfileCredential(credential: AuthProfileCredential): boolean {
if (credential.type === "api_key") {
return Boolean(readNonEmptyString(credential.key) || credential.keyRef);
}
if (credential.type === "token") {
return Boolean(readNonEmptyString(credential.token) || credential.tokenRef);
}
return (
Boolean(readNonEmptyString(credential.access)) &&
Boolean(readNonEmptyString(credential.refresh)) &&
typeof credential.expires === "number"
);
}
function mergeImportedAuthProfiles(params: {
store: AuthProfileStore;
profiles: AuthProfileStore["profiles"];
existingProfileIds: ReadonlySet<string>;
replaceExistingWithoutCredential?: boolean;
}): AuthProfileStore {
const profiles = { ...params.store.profiles };
for (const [profileId, credential] of Object.entries(params.profiles)) {
if (!params.existingProfileIds.has(profileId)) {
profiles[profileId] = credential;
continue;
}
const existing = profiles[profileId];
if (
params.replaceExistingWithoutCredential &&
existing &&
!hasUsableAuthProfileCredential(existing) &&
hasUsableAuthProfileCredential(credential)
) {
profiles[profileId] = credential;
}
}
return { ...params.store, profiles };
@ -492,6 +742,14 @@ function hasImportableAuthProfileStore(store: AuthProfileStore | null): store is
return Boolean(store && (Object.keys(store.profiles).length > 0 || hasAuthProfileState(store)));
}
function hasLegacyAuthProfileSource(candidate: AuthProfileSqliteMigrationCandidate): boolean {
return (
fs.existsSync(candidate.authPath) ||
fs.existsSync(candidate.statePath) ||
fs.existsSync(candidate.legacyPath)
);
}
function backupAuthProfileJson(pathname: string, suffix: string, now: () => number): string {
const backupPath = `${pathname}.${suffix}.${now()}.bak`;
fs.copyFileSync(pathname, backupPath);
@ -532,17 +790,30 @@ export async function maybeMigrateAuthProfileJsonStoresToSqlite(params: {
const loadMigratedStore =
params.deps?.loadPersistedAuthProfileStore ?? loadPersistedAuthProfileStore;
const candidates = listAuthProfileSqliteMigrationCandidates(params.cfg, env);
const configStore = coerceLegacyConfigAuthProfileStore(params.cfg);
const detected = candidates.filter(
(candidate) =>
fs.existsSync(candidate.authPath) ||
fs.existsSync(candidate.statePath) ||
fs.existsSync(candidate.legacyPath),
hasLegacyAuthProfileSource(candidate) ||
(configStore && isDefaultAgentCandidate(candidate, params.cfg, env)),
);
const result: LegacyFlatAuthProfileRepairResult = {
detected: detected.flatMap((candidate) =>
[candidate.authPath, candidate.statePath, candidate.legacyPath].filter((pathname) =>
fs.existsSync(pathname),
),
[
candidate.authPath,
candidate.statePath,
candidate.legacyPath,
...(configStore && isDefaultAgentCandidate(candidate, params.cfg, env)
? [candidate.authPath]
: []),
]
.filter((pathname, index, entries) => entries.indexOf(pathname) === index)
.filter(
(pathname) =>
fs.existsSync(pathname) ||
(configStore &&
isDefaultAgentCandidate(candidate, params.cfg, env) &&
pathname === candidate.authPath),
),
),
changes: [],
warnings: [],
@ -615,12 +886,20 @@ export async function maybeMigrateAuthProfileJsonStoresToSqlite(params: {
const canonicalStore = hasImportableAuthProfileStore(maybeCanonicalStore)
? maybeCanonicalStore
: null;
const configCanonicalStore =
configStore && isDefaultAgentCandidate(candidate, params.cfg, env) ? configStore : null;
const legacyStore = loadLegacyAuthProfileStore(candidate.agentDir);
const rawState = fs.existsSync(candidate.statePath)
? loadJsonFile(candidate.statePath)
: null;
const state = coerceAuthProfileState(rawState);
if (!canonicalStore && !legacyStore && !hasAuthProfileState(state) && !awsSdkMarkerStore) {
if (
!canonicalStore &&
!configCanonicalStore &&
!legacyStore &&
!hasAuthProfileState(state) &&
!awsSdkMarkerStore
) {
result.warnings.push(
`Left auth profile JSON in place for ${shortenHomePath(candidate.authPath)} because no importable auth profiles or state were found.`,
);
@ -666,16 +945,32 @@ export async function maybeMigrateAuthProfileJsonStoresToSqlite(params: {
existingState,
});
}
if (configCanonicalStore) {
for (const profileId of Object.keys(configCanonicalStore.profiles)) {
importedProfileIds.add(profileId);
}
// Config imports fill missing SQLite credentials only; when both exist,
// the canonical per-agent SQLite store wins over legacy config secrets.
next = mergeImportedAuthProfiles({
store: next,
profiles: configCanonicalStore.profiles,
existingProfileIds: new Set(Object.keys(next.profiles)),
replaceExistingWithoutCredential: true,
});
}
if (hasAuthProfileState(state)) {
next = mergeImportedAuthProfileState({ store: next, state, existingState });
}
if (canonicalStore || legacyStore || hasAuthProfileState(state)) {
if (canonicalStore || configCanonicalStore || legacyStore || hasAuthProfileState(state)) {
const stateProfileIds = [
...collectAuthProfileStateProfileIds(state),
...(canonicalStore
? collectAuthProfileStateProfileIds(coerceAuthProfileState(canonicalStore))
: []),
...(configCanonicalStore
? collectAuthProfileStateProfileIds(coerceAuthProfileState(configCanonicalStore))
: []),
];
saveAuthProfileStore(next, candidate.agentDir, {
filterExternalAuthProfiles: false,
@ -694,6 +989,12 @@ export async function maybeMigrateAuthProfileJsonStoresToSqlite(params: {
);
continue;
}
if (
configCanonicalStore &&
stripImportedConfigAuthProfileCredentials(params.cfg, configCanonicalStore)
) {
result.configChanged = true;
}
}
const backups: string[] = [];
@ -711,8 +1012,12 @@ export async function maybeMigrateAuthProfileJsonStoresToSqlite(params: {
if (fs.existsSync(candidate.legacyPath)) {
backups.push(backupAndRemoveAuthProfileJson(candidate.legacyPath, "sqlite-import", now));
}
const backupText =
backups.length > 0
? `backup${backups.length === 1 ? "" : "s"}: ${backups.map(shortenHomePath).join(", ")}`
: "no legacy JSON backup needed";
result.changes.push(
`Migrated auth profile JSON for ${shortenHomePath(candidate.authPath)} into SQLite (backup${backups.length === 1 ? "" : "s"}: ${backups.map(shortenHomePath).join(", ")}).`,
`Migrated auth profile JSON for ${shortenHomePath(candidate.authPath)} into SQLite (${backupText}).`,
);
if (awsSdkMarkerStore) {
result.changes.push(