@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [0.67.5] - 2026-04-16
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
|
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||||
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
||||||
import { DynamicBorder, getAgentDir } from "@mariozechner/pi-coding-agent";
|
import { DynamicBorder, getAgentDir } from "@mariozechner/pi-coding-agent";
|
||||||
import { Container, Key, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
|
import { Container, Key, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
|
||||||
@@ -98,7 +99,7 @@ function loadPresets(cwd: string): PresetsConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface OriginalState {
|
interface OriginalState {
|
||||||
model: import("@mariozechner/pi-coding-agent").Model<any> | undefined;
|
model: Model<Api> | undefined;
|
||||||
thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||||
tools: string[];
|
tools: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import type {
|
|||||||
import { FooterDataProvider, type ReadonlyFooterDataProvider } from "../../core/footer-data-provider.js";
|
import { FooterDataProvider, type ReadonlyFooterDataProvider } from "../../core/footer-data-provider.js";
|
||||||
import { type AppKeybinding, KeybindingsManager } from "../../core/keybindings.js";
|
import { type AppKeybinding, KeybindingsManager } from "../../core/keybindings.js";
|
||||||
import { createCompactionSummaryMessage } from "../../core/messages.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 { DefaultPackageManager } from "../../core/package-manager.js";
|
||||||
import type { ResourceDiagnostic } from "../../core/resource-loader.js";
|
import type { ResourceDiagnostic } from "../../core/resource-loader.js";
|
||||||
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.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";
|
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUnknownModel(model: Model<any> | 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.
|
* Options for InteractiveMode initialization.
|
||||||
*/
|
*/
|
||||||
@@ -554,7 +562,7 @@ export class InteractiveMode {
|
|||||||
this.builtInHeader = new ExpandableText(
|
this.builtInHeader = new ExpandableText(
|
||||||
() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`,
|
() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`,
|
||||||
() => `${logo}\n${expandedInstructions}\n\n${onboarding}`,
|
() => `${logo}\n${expandedInstructions}\n\n${onboarding}`,
|
||||||
this.toolOutputExpanded,
|
this.getStartupExpansionState(),
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
@@ -886,6 +894,10 @@ export class InteractiveMode {
|
|||||||
return this.formatDisplayPath(absolutePath);
|
return this.formatDisplayPath(absolutePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getStartupExpansionState(): boolean {
|
||||||
|
return this.options.verbose || this.toolOutputExpanded;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a short path relative to the package root for display.
|
* Get a short path relative to the package root for display.
|
||||||
*/
|
*/
|
||||||
@@ -1132,7 +1144,7 @@ export class InteractiveMode {
|
|||||||
const section = new ExpandableText(
|
const section = new ExpandableText(
|
||||||
() => `${sectionHeader(name, color)}\n${collapsedBody}`,
|
() => `${sectionHeader(name, color)}\n${collapsedBody}`,
|
||||||
() => `${sectionHeader(name, color)}\n${expandedBody}`,
|
() => `${sectionHeader(name, color)}\n${expandedBody}`,
|
||||||
this.toolOutputExpanded,
|
this.getStartupExpansionState(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
@@ -4124,6 +4136,7 @@ export class InteractiveMode {
|
|||||||
private async showLoginDialog(providerId: string): Promise<void> {
|
private async showLoginDialog(providerId: string): Promise<void> {
|
||||||
const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId);
|
const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId);
|
||||||
const providerName = providerInfo?.name || providerId;
|
const providerName = providerInfo?.name || providerId;
|
||||||
|
const previousModel = this.session.model;
|
||||||
|
|
||||||
// Providers that use callback servers (can paste redirect URL)
|
// Providers that use callback servers (can paste redirect URL)
|
||||||
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
||||||
@@ -4199,8 +4212,50 @@ export class InteractiveMode {
|
|||||||
// Success
|
// Success
|
||||||
restoreEditor();
|
restoreEditor();
|
||||||
this.session.modelRegistry.refresh();
|
this.session.modelRegistry.refresh();
|
||||||
|
|
||||||
|
let selectedModel: Model<any> | 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();
|
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) {
|
} catch (error: unknown) {
|
||||||
restoreEditor();
|
restoreEditor();
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
},
|
},
|
||||||
formatDisplayPath: (p: string) => (InteractiveMode as any).prototype.formatDisplayPath.call(fakeThis, p),
|
formatDisplayPath: (p: string) => (InteractiveMode as any).prototype.formatDisplayPath.call(fakeThis, p),
|
||||||
formatContextPath: (p: string) => (InteractiveMode as any).prototype.formatContextPath.call(fakeThis, p),
|
formatContextPath: (p: string) => (InteractiveMode as any).prototype.formatContextPath.call(fakeThis, p),
|
||||||
|
getStartupExpansionState: () => (InteractiveMode as any).prototype.getStartupExpansionState.call(fakeThis),
|
||||||
buildScopeGroups: () => [],
|
buildScopeGroups: () => [],
|
||||||
formatScopeGroups: () => "resource-list",
|
formatScopeGroups: () => "resource-list",
|
||||||
getShortPath: (p: string) => p,
|
getShortPath: (p: string) => p,
|
||||||
@@ -216,6 +217,24 @@ describe("InteractiveMode.showLoadedResources", () => {
|
|||||||
expect(output).not.toContain("commit");
|
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", () => {
|
test("abbreviates extensions in compact listing", () => {
|
||||||
const fakeThis = createShowLoadedResourcesThis({
|
const fakeThis = createShowLoadedResourcesThis({
|
||||||
quietStartup: false,
|
quietStartup: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user