fix: remove duplicate TUI header rendered on session_start (#1663)

This commit is contained in:
Jeremy McSpadden 2026-03-21 09:34:18 -05:00 committed by GitHub
parent ee7c6b5c2b
commit 0997b4945d
5 changed files with 29 additions and 72 deletions

View file

@ -323,11 +323,13 @@ function pruneRemovedBundledExtensions(
for (const prevFile of manifest.installedExtensionRootFiles) {
removeIfStale(prevFile)
}
} else {
// Fallback: explicitly remove known stale files from pre-manifest-tracking versions
// env-utils.js was moved from extensions/ root → gsd/ in v2.39.x (#1634)
removeIfStale('env-utils.js')
}
// Always remove known stale files regardless of manifest state.
// These were installed by pre-manifest versions so they may not appear in
// installedExtensionRootFiles even when a manifest exists.
// env-utils.js was moved from extensions/ root → gsd/ in v2.39.x (#1634)
removeIfStale('env-utils.js')
}
/**

View file

@ -14,12 +14,28 @@ import { deriveState } from "../state.js";
import { getAutoDashboardData, isAutoActive, isAutoPaused, markToolEnd, markToolStart } from "../auto.js";
import { isParallelActive, shutdownParallel } from "../parallel-orchestrator.js";
import { saveActivityLog } from "../activity-log.js";
import { maybeRenderGsdHeader } from "./register-shortcuts.js";
// Skip the welcome screen on the very first session_start — cli.ts already
// printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
let isFirstSession = true;
export function registerHooks(pi: ExtensionAPI): void {
pi.on("session_start", async (_event, ctx) => {
resetWriteGateState();
maybeRenderGsdHeader(ctx);
if (isFirstSession) {
isFirstSession = false;
} else {
try {
const gsdBinPath = process.env.GSD_BIN_PATH;
if (gsdBinPath) {
const { dirname } = await import('node:path');
const { printWelcomeScreen } = await import(
join(dirname(gsdBinPath), 'welcome-screen.js')
) as { printWelcomeScreen: (opts: { version: string; modelName?: string; provider?: string }) => void };
printWelcomeScreen({ version: process.env.GSD_VERSION || '0.0.0' });
}
} catch { /* non-fatal */ }
}
loadToolApiKeys();
try {
const [{ getRemoteConfigStatus }, { getLatestPromptSummary }] = await Promise.all([

View file

@ -2,20 +2,11 @@ import { existsSync } from "node:fs";
import { join } from "node:path";
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
import { Key, Text } from "@gsd/pi-tui";
import { Key } from "@gsd/pi-tui";
import { GSDDashboardOverlay } from "../dashboard-overlay.js";
import { shortcutDesc } from "../../shared/mod.js";
export const GSD_LOGO_LINES = [
" ██████╗ ███████╗██████╗ ",
" ██╔════╝ ██╔════╝██╔══██╗",
" ██║ ███╗███████╗██║ ██║",
" ██║ ██║╚════██║██║ ██║",
" ╚██████╔╝███████║██████╔╝",
" ╚═════╝ ╚══════╝╚═════╝ ",
];
export function registerShortcuts(pi: ExtensionAPI): void {
pi.registerShortcut(Key.ctrlAlt("g"), {
description: shortcutDesc("Open GSD dashboard", "/gsd status"),
@ -39,17 +30,3 @@ export function registerShortcuts(pi: ExtensionAPI): void {
},
});
}
export function maybeRenderGsdHeader(ctx: { ui: any }): void {
try {
const theme = ctx.ui.theme;
const version = process.env.GSD_VERSION || "0.0.0";
const logoText = GSD_LOGO_LINES.map((line) => theme.fg("accent", line)).join("\n");
const titleLine = ` ${theme.bold("Get Shit Done")} ${theme.fg("dim", `v${version}`)}`;
const headerContent = `${logoText}\n${titleLine}`;
ctx.ui.setHeader((_ui: unknown, _theme: unknown) => new Text(headerContent, 1, 0));
} catch {
// no TUI
}
}

View file

@ -216,23 +216,9 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
return payload;
});
// Basic startup diagnostics — provider-specific info comes from model_select
pi.on("session_start", async (_event: any, ctx: any) => {
pi.on("session_start", async (_event: any, _ctx: any) => {
// Reset session-level search budget (#1309)
sessionSearchCount = 0;
const hasBrave = !!process.env.BRAVE_API_KEY;
const hasJina = !!process.env.JINA_API_KEY;
const hasAnswers = !!process.env.BRAVE_ANSWERS_KEY;
const hasTavily = !!process.env.TAVILY_API_KEY;
const parts: string[] = ["Web search v4 loaded"];
if (hasBrave) parts.push("Brave ✓");
if (hasAnswers) parts.push("Answers ✓");
if (hasJina) parts.push("Jina ✓");
if (hasTavily) parts.push("Tavily ✓");
ctx.ui.notify(parts.join(" · "), "info");
});
return { getIsAnthropic: () => isAnthropicProvider };

View file

@ -433,41 +433,17 @@ test("model_select shows warning for non-Anthropic without Brave key", async ()
}
});
test("session_start shows v4 loaded message", async () => {
test("session_start resets search count and shows no startup notification", async () => {
const pi = createMockPI();
registerNativeSearchHooks(pi);
await pi.fire("session_start", { type: "session_start" });
// Tool status is now shown in the welcome screen bar layout — no notification on session_start
const infoNotif = pi.notifications.find(
(n) => n.level === "info" && n.message.includes("v4")
);
assert.ok(infoNotif, "Should have v4 info notification");
assert.ok(
infoNotif!.message.startsWith("Web search v4 loaded"),
`Should start with 'Web search v4 loaded' — got: ${infoNotif!.message}`
);
});
test("session_start shows Brave status when key present", async () => {
const originalKey = process.env.BRAVE_API_KEY;
process.env.BRAVE_API_KEY = "test-key";
try {
const pi = createMockPI();
registerNativeSearchHooks(pi);
await pi.fire("session_start", { type: "session_start" });
const info = pi.notifications.find((n) => n.level === "info");
assert.ok(info!.message.includes("Brave"), "Should mention Brave in status");
const warning = pi.notifications.find((n) => n.level === "warning");
assert.equal(warning, undefined, "Should NOT show warning when Brave key is present");
} finally {
if (originalKey) process.env.BRAVE_API_KEY = originalKey;
else delete process.env.BRAVE_API_KEY;
}
assert.equal(infoNotif, undefined, "Should NOT emit a v4 startup notification (welcome screen handles this)");
});
test("BRAVE_TOOL_NAMES contains expected tool names", () => {