From fa4670c5fe50feb2c4afad3935cc2c7809401274 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 15 Jan 2026 04:41:50 +0000 Subject: [PATCH] feat: improve agent auth guidance --- CHANGELOG.md | 1 + src/agents/model-auth.ts | 13 ++++++++- src/agents/pi-embedded-runner/compact.ts | 1 + src/agents/pi-embedded-runner/run.ts | 1 + src/commands/agents.commands.add.ts | 35 +++++++++++++++++++++++- 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2474b56df..23aeb2c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changes - Docs: clarify per-agent auth stores, sandboxed skill binaries, and elevated semantics. - Docs: add FAQ entries for missing provider auth after adding agents and Gemini thinking signature errors. +- Agents: add optional auth-profile copy prompt on `agents add` and improve auth error messaging. - Security: add `clawdbot security audit` (`--deep`, `--fix`) and surface it in `status --all` and `doctor`. - Onboarding: add a security checkpoint prompt (docs link + sandboxing hint); require `--accept-risk` for `--non-interactive`. - Docs: expand gateway security hardening guidance and incident response checklist. diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index be59e42f0..20e534672 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -1,3 +1,5 @@ +import path from "node:path"; + import { type Api, getEnvApiKey, type Model } from "@mariozechner/pi-ai"; import type { ClawdbotConfig } from "../config/config.js"; import type { ModelProviderConfig } from "../config/types.js"; @@ -8,6 +10,7 @@ import { listProfilesForProvider, resolveApiKeyForProfile, resolveAuthProfileOrder, + resolveAuthStorePathForDisplay, } from "./auth-profiles.js"; import { normalizeProviderId } from "./model-selection.js"; @@ -94,7 +97,15 @@ export async function resolveApiKeyForProvider(params: { } } - throw new Error(`No API key found for provider "${provider}".`); + const authStorePath = resolveAuthStorePathForDisplay(params.agentDir); + const resolvedAgentDir = path.dirname(authStorePath); + throw new Error( + [ + `No API key found for provider "${provider}".`, + `Auth store: ${authStorePath} (agentDir: ${resolvedAgentDir}).`, + "Configure auth for this agent (clawdbot agents add ) or copy auth-profiles.json from the main agentDir.", + ].join(" "), + ); } export type EnvApiKeyResult = { apiKey: string; source: string }; diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index f51880322..8c88c1945 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -113,6 +113,7 @@ export async function compactEmbeddedPiSession(params: { const apiKeyInfo = await getApiKeyForModel({ model, cfg: params.config, + agentDir, }); if (model.provider === "github-copilot") { diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index 6cbb44045..8918a790c 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -133,6 +133,7 @@ export async function runEmbeddedPiAgent( cfg: params.config, profileId: candidate, store: authStore, + agentDir, }); }; diff --git a/src/commands/agents.commands.add.ts b/src/commands/agents.commands.add.ts index f92d6c351..e0fe40a18 100644 --- a/src/commands/agents.commands.add.ts +++ b/src/commands/agents.commands.add.ts @@ -1,5 +1,9 @@ -import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; +import fs from "node:fs/promises"; +import path from "node:path"; + +import { resolveAgentDir, resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; import { ensureAuthProfileStore } from "../agents/auth-profiles.js"; +import { resolveAuthStorePath } from "../agents/auth-profiles/paths.js"; import { CONFIG_PATH_CLAWDBOT, writeConfigFile } from "../config/config.js"; import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -31,6 +35,15 @@ type AgentsAddOptions = { json?: boolean; }; +async function fileExists(pathname: string): Promise { + try { + await fs.stat(pathname); + return true; + } catch { + return false; + } +} + export async function agentsAddCommand( opts: AgentsAddOptions, runtime: RuntimeEnv = defaultRuntime, @@ -205,6 +218,26 @@ export async function agentsAddCommand( agentDir, }); + const defaultAgentId = resolveDefaultAgentId(cfg); + if (defaultAgentId !== agentId) { + const sourceAuthPath = resolveAuthStorePath(resolveAgentDir(cfg, defaultAgentId)); + const destAuthPath = resolveAuthStorePath(agentDir); + const sameAuthPath = + path.resolve(sourceAuthPath).toLowerCase() === + path.resolve(destAuthPath).toLowerCase(); + if (!sameAuthPath && (await fileExists(sourceAuthPath)) && !(await fileExists(destAuthPath))) { + const shouldCopy = await prompter.confirm({ + message: `Copy auth profiles from "${defaultAgentId}"?`, + initialValue: false, + }); + if (shouldCopy) { + await fs.mkdir(path.dirname(destAuthPath), { recursive: true }); + await fs.copyFile(sourceAuthPath, destAuthPath); + await prompter.note(`Copied auth profiles from "${defaultAgentId}".`, "Auth profiles"); + } + } + } + const wantsAuth = await prompter.confirm({ message: "Configure model/auth for this agent now?", initialValue: false,