diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c1747b16..eea5c9e3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `--verbose` startup output to begin with expanded startup help and loaded resource listings after the compact startup header change ([#3147](https://github.com/badlogic/pi-mono/issues/3147)) + ## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/coding-agent/examples/extensions/preset.ts b/packages/coding-agent/examples/extensions/preset.ts index 039c637b..a0cf663e 100644 --- a/packages/coding-agent/examples/extensions/preset.ts +++ b/packages/coding-agent/examples/extensions/preset.ts @@ -40,6 +40,7 @@ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; +import type { Api, Model } from "@mariozechner/pi-ai"; import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; import { DynamicBorder, getAgentDir } from "@mariozechner/pi-coding-agent"; import { Container, Key, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui"; @@ -98,7 +99,7 @@ function loadPresets(cwd: string): PresetsConfig { } interface OriginalState { - model: import("@mariozechner/pi-coding-agent").Model | undefined; + model: Model | undefined; thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; tools: string[]; } diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 3e148c44..4630c527 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -58,7 +58,7 @@ import type { import { FooterDataProvider, type ReadonlyFooterDataProvider } from "../../core/footer-data-provider.js"; import { type AppKeybinding, KeybindingsManager } from "../../core/keybindings.js"; import { createCompactionSummaryMessage } from "../../core/messages.js"; -import { findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js"; +import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js"; import { DefaultPackageManager } from "../../core/package-manager.js"; import type { ResourceDiagnostic } from "../../core/resource-loader.js"; import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js"; @@ -159,6 +159,14 @@ function isTruthyEnvFlag(value: string | undefined): boolean { return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes"; } +function isUnknownModel(model: Model | undefined): boolean { + return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown"; +} + +function hasDefaultModelProvider(providerId: string): providerId is keyof typeof defaultModelPerProvider { + return providerId in defaultModelPerProvider; +} + /** * Options for InteractiveMode initialization. */ @@ -554,7 +562,7 @@ export class InteractiveMode { this.builtInHeader = new ExpandableText( () => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, - this.toolOutputExpanded, + this.getStartupExpansionState(), 1, 0, ); @@ -886,6 +894,10 @@ export class InteractiveMode { return this.formatDisplayPath(absolutePath); } + private getStartupExpansionState(): boolean { + return this.options.verbose || this.toolOutputExpanded; + } + /** * Get a short path relative to the package root for display. */ @@ -1132,7 +1144,7 @@ export class InteractiveMode { const section = new ExpandableText( () => `${sectionHeader(name, color)}\n${collapsedBody}`, () => `${sectionHeader(name, color)}\n${expandedBody}`, - this.toolOutputExpanded, + this.getStartupExpansionState(), 0, 0, ); @@ -4124,6 +4136,7 @@ export class InteractiveMode { private async showLoginDialog(providerId: string): Promise { const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId); const providerName = providerInfo?.name || providerId; + const previousModel = this.session.model; // Providers that use callback servers (can paste redirect URL) const usesCallbackServer = providerInfo?.usesCallbackServer ?? false; @@ -4199,8 +4212,50 @@ export class InteractiveMode { // Success restoreEditor(); this.session.modelRegistry.refresh(); + + let selectedModel: Model | undefined; + let selectionError: string | undefined; + if (isUnknownModel(previousModel)) { + const availableModels = this.session.modelRegistry.getAvailable(); + const providerModels = availableModels.filter((model) => model.provider === providerId); + if (!hasDefaultModelProvider(providerId)) { + selectionError = `Logged in to ${providerName}, but no default model is configured for provider "${providerId}". Use /model to select a model.`; + } else if (providerModels.length === 0) { + selectionError = `Logged in to ${providerName}, but no models are available for that provider. Use /model to select a model.`; + } else { + const defaultModelId = defaultModelPerProvider[providerId]; + selectedModel = providerModels.find((model) => model.id === defaultModelId); + if (!selectedModel) { + selectionError = `Logged in to ${providerName}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`; + } else { + try { + await this.session.setModel(selectedModel); + } catch (error: unknown) { + selectedModel = undefined; + const errorMessage = error instanceof Error ? error.message : String(error); + selectionError = `Logged in to ${providerName}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`; + } + } + } + } + await this.updateAvailableProviderCount(); - this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`); + this.footer.invalidate(); + this.updateEditorBorderColor(); + if (selectedModel) { + this.showStatus( + `Logged in to ${providerName}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`, + ); + void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel); + this.checkDaxnutsEasterEgg(selectedModel); + } else { + this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`); + if (selectionError) { + this.showError(selectionError); + } else { + void this.maybeWarnAboutAnthropicSubscriptionAuth(); + } + } } catch (error: unknown) { restoreEditor(); const errorMsg = error instanceof Error ? error.message : String(error); diff --git a/packages/coding-agent/test/interactive-mode-status.test.ts b/packages/coding-agent/test/interactive-mode-status.test.ts index 6dfa8709..e2af115b 100644 --- a/packages/coding-agent/test/interactive-mode-status.test.ts +++ b/packages/coding-agent/test/interactive-mode-status.test.ts @@ -173,6 +173,7 @@ describe("InteractiveMode.showLoadedResources", () => { }, formatDisplayPath: (p: string) => (InteractiveMode as any).prototype.formatDisplayPath.call(fakeThis, p), formatContextPath: (p: string) => (InteractiveMode as any).prototype.formatContextPath.call(fakeThis, p), + getStartupExpansionState: () => (InteractiveMode as any).prototype.getStartupExpansionState.call(fakeThis), buildScopeGroups: () => [], formatScopeGroups: () => "resource-list", getShortPath: (p: string) => p, @@ -216,6 +217,24 @@ describe("InteractiveMode.showLoadedResources", () => { expect(output).not.toContain("commit"); }); + test("shows full resource listing on verbose startup even when tool output is collapsed", () => { + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: true, + verbose: true, + toolOutputExpanded: false, + skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }], + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + const output = renderAll(fakeThis.chatContainer); + expect(output).toContain("[Skills]"); + expect(output).toContain("resource-list"); + expect(output).not.toContain("commit"); + }); + test("abbreviates extensions in compact listing", () => { const fakeThis = createShowLoadedResourcesThis({ quietStartup: false,