fix(coding-agent): persist implicit project trust on reload

This commit is contained in:
Mario Zechner
2026-06-08 10:08:01 +02:00
parent 130ae577ac
commit 38f18be447
5 changed files with 51 additions and 5 deletions

View File

@@ -94,9 +94,13 @@ function withTrustFileLock<T>(path: string, fn: () => T): T {
}
}
export function hasProjectConfigDir(cwd: string): boolean {
return existsSync(join(canonicalizePath(resolvePath(cwd)), CONFIG_DIR_NAME));
}
export function hasProjectTrustInputs(cwd: string): boolean {
let currentDir = canonicalizePath(resolvePath(cwd));
if (existsSync(join(currentDir, CONFIG_DIR_NAME))) {
if (hasProjectConfigDir(currentDir)) {
return true;
}

View File

@@ -647,9 +647,12 @@ export async function main(args: string[], options?: MainOptions) {
time("createSessionManager");
const trustStore = new ProjectTrustStore(agentDir);
const sessionCwd = sessionManager.getCwd();
const autoTrustOnReloadCwd =
parsed.projectTrustOverride === undefined && !hasProjectTrustInputs(sessionCwd) ? sessionCwd : undefined;
const trustPromptMode: AppMode = parsed.help || parsed.listModels !== undefined ? "print" : appMode;
const projectTrustedForSession = await resolveProjectTrusted({
cwd: sessionManager.getCwd(),
cwd: sessionCwd,
trustStore,
trustOverride: parsed.projectTrustOverride,
appMode: trustPromptMode,
@@ -827,6 +830,7 @@ export async function main(args: string[], options?: MainOptions) {
const interactiveMode = new InteractiveMode(runtime, {
migratedProviders,
modelFallbackMessage,
autoTrustOnReloadCwd,
initialMessage,
initialImages,
initialMessages: parsed.messages,

View File

@@ -85,7 +85,7 @@ import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.ts";
import type { SourceInfo } from "../../core/source-info.ts";
import { isInstallTelemetryEnabled } from "../../core/telemetry.ts";
import type { TruncationResult } from "../../core/tools/truncate.ts";
import { hasProjectTrustInputs, ProjectTrustStore } from "../../core/trust-manager.ts";
import { hasProjectConfigDir, hasProjectTrustInputs, ProjectTrustStore } from "../../core/trust-manager.ts";
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.ts";
import { copyToClipboard } from "../../utils/clipboard.ts";
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.ts";
@@ -249,6 +249,8 @@ export interface InteractiveModeOptions {
migratedProviders?: string[];
/** Warning message if session model couldn't be restored */
modelFallbackMessage?: string;
/** Cwd to trust after reload if it gained a .pi directory during this implicitly trusted session. */
autoTrustOnReloadCwd?: string;
/** Initial message to send on startup (can include @file content) */
initialMessage?: string;
/** Images to attach to the initial message */
@@ -367,6 +369,7 @@ export class InteractiveMode {
private customHeader: (Component & { dispose?(): void }) | undefined = undefined;
private options: InteractiveModeOptions;
private autoTrustOnReloadCwd: string | undefined;
// Convenience accessors
private get session(): AgentSession {
@@ -385,6 +388,7 @@ export class InteractiveMode {
constructor(runtimeHost: AgentSessionRuntime, options: InteractiveModeOptions = {}) {
this.runtimeHost = runtimeHost;
this.options = options;
this.autoTrustOnReloadCwd = options.autoTrustOnReloadCwd;
this.runtimeHost.setBeforeSessionInvalidate(() => {
this.resetExtensionUI();
});
@@ -4163,6 +4167,32 @@ export class InteractiveMode {
}
}
private maybeSaveImplicitProjectTrustAfterReload(): boolean {
const cwd = this.sessionManager.getCwd();
if (this.autoTrustOnReloadCwd !== cwd) {
return false;
}
if (!this.settingsManager.isProjectTrusted() || !hasProjectConfigDir(cwd)) {
return false;
}
const trustStore = new ProjectTrustStore(this.runtimeHost.services.agentDir);
try {
if (trustStore.get(cwd) !== null) {
this.autoTrustOnReloadCwd = undefined;
return false;
}
trustStore.set(cwd, true);
this.autoTrustOnReloadCwd = undefined;
return true;
} catch (error) {
this.showWarning(
`Could not save project trust after reload: ${error instanceof Error ? error.message : String(error)}`,
);
return false;
}
}
private showTrustSelector(): void {
const cwd = this.sessionManager.getCwd();
const trustStore = new ProjectTrustStore(this.runtimeHost.services.agentDir);
@@ -5047,11 +5077,16 @@ export class InteractiveMode {
force: false,
showDiagnosticsWhenQuiet: true,
});
const savedImplicitProjectTrust = this.maybeSaveImplicitProjectTrustAfterReload();
const modelsJsonError = this.session.modelRegistry.getError();
if (modelsJsonError) {
this.showError(`models.json error: ${modelsJsonError}`);
}
this.showStatus("Reloaded keybindings, extensions, skills, prompts, themes");
this.showStatus(
savedImplicitProjectTrust
? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
: "Reloaded keybindings, extensions, skills, prompts, themes",
);
} catch (error) {
dismissReloadBox(previousEditor as Component);
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);