Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions commands/agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { AgentCallbacks, TurnSummary } from "../config/types";
import { App, store } from "../tui/src";
import { render } from "ink";
import { AgentController } from "./agentController";
import { ACTIVE_PROVIDER_MODELS, DEFAULT_MODEL_ID, getModelDisplayName } from "../providers/client";
import { DEFAULT_MODEL_ID } from "../providers/client";
import type { HomeScreenData } from "../tui/src/components/HomeScreen";
import { ensureProviderConfigured } from "../onboarding";
import { registerCommands } from "./slash";
Expand Down Expand Up @@ -317,7 +317,7 @@ async function runInteractive(modelOverride?: string) {
};
const controller = new AgentController(provider, apiKey, selectedModel, callbacks);
await controller.initialize();
const homeScreen = await buildHomeScreen(provider, selectedModel);
const homeScreen = await buildHomeScreen(provider);

const customStdin = new PassThrough() as any;
customStdin.ref = () => {
Expand Down Expand Up @@ -437,9 +437,7 @@ async function runInteractive(modelOverride?: string) {
});
}

async function buildHomeScreen(provider: string, model: string): Promise<HomeScreenData> {
const repository =
process.cwd().split("/").filter(Boolean).at(-1) ?? "workspace";
async function buildHomeScreen(provider: string): Promise<HomeScreenData> {
const branch = await getBranch();
const providerLabel = provider === "google" ? "Gemini" : titleCase(provider);

Expand All @@ -464,13 +462,8 @@ async function buildHomeScreen(provider: string, model: string): Promise<HomeScr
"Test",
"Document",
],
repository,
branch,
providerName: providerLabel,
// getModelDisplayName resolves through the whole catalog now, so every
// provider's models name themselves; the Google-only branch this replaced
// fell back to a hardcoded label for anything else.
provider: getModelDisplayName(model) ?? providerLabel,
};
}

Expand Down
24 changes: 7 additions & 17 deletions config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import { existsSync, mkdirSync } from "fs";
/**
* Returns the user configuration directory for Woopcode.
* Creates the directory if it doesn't exist.
*
*
* Location:
* - macOS/Linux: ~/.config/woopcode/
* - Windows: %LOCALAPPDATA%\woopcode\
*/
export function getConfigDir(): string {
const home = homedir();

let configDir: string;

if (process.platform === "win32") {
// Windows: Use LOCALAPPDATA
const localAppData = process.env.LOCALAPPDATA || join(home, "AppData", "Local");
Expand All @@ -24,25 +24,19 @@ export function getConfigDir(): string {
const xdgConfigHome = process.env.XDG_CONFIG_HOME || join(home, ".config");
configDir = join(xdgConfigHome, "woopcode");
}

// Ensure directory exists
if (!existsSync(configDir)) {
mkdirSync(configDir, { recursive: true });
}

return configDir;
}

/**
* Get the path to providers.json
*/
export function getProvidersConfigPath(): string {
return join(getConfigDir(), "providers.json");
}

/**
* Get the path to conversation.json
*/
export function getConversationPath(): string {
return join(getConfigDir(), "conversation.json");
}
Expand All @@ -58,9 +52,6 @@ export function getExecutionLogPath(): string {
return join(getConfigDir(), "execution-log.json");
}

/**
* Get the path to models.json
*/
export function getModelsPath(): string {
return join(getConfigDir(), "models.json");
}
Expand All @@ -69,10 +60,9 @@ export function getModelsPath(): string {
* Initialize configuration directory with default files if they don't exist.
*/
export async function initializeConfig(): Promise<void> {
const configDir = getConfigDir();
const providersPath = getProvidersConfigPath();
const conversationPath = getConversationPath();

// Create default providers.json if it doesn't exist
if (!existsSync(providersPath)) {
const defaultProviders = {
Expand All @@ -92,7 +82,7 @@ export async function initializeConfig(): Promise<void> {
}
}
};

await Bun.write(providersPath, JSON.stringify(defaultProviders, null, 2));
} else {
await removeRetiredProviders(providersPath);
Expand Down
3 changes: 1 addition & 2 deletions onboarding/setupWizard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { useState } from "react";
import { useState } from "react";
import { Box, Text, useInput } from "ink";
import TextInput from "ink-text-input";
import Spinner from "ink-spinner";
import chalk from "chalk";
import { getEnabledProviders, type ProviderInfo } from "../providers/providerRegistry";
import { loginProvider } from "../config/authProvider";
import { getConfig, saveConfig } from "../config/config";
Expand Down
6 changes: 1 addition & 5 deletions packages/tests/e2e/chat.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { describe, test, expect, beforeEach } from "bun:test";
import { AgentController } from "../../../commands/agentController";
import { MockProviderClient, CallbackSpy } from "../shared/mocks";
import {
createTextEvent,
createDoneEvent,
createUserMessage,
} from "../shared/factories";
import { createTextEvent, createDoneEvent } from "../shared/factories";

/**
* End-to-End Chat Workflow Tests
Expand Down
2 changes: 1 addition & 1 deletion packages/tests/runtime/agentController.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, test, expect, beforeEach, afterEach, afterAll, mock } from "bun:test";
import { describe, test, expect, beforeEach, afterAll, mock } from "bun:test";
import { AgentController } from "../../../commands/agentController";
import type { Message } from "../../../config/types";
import {
Expand Down
2 changes: 1 addition & 1 deletion packages/tests/runtime/agentLoop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
createDoneEvent,
generateLargeText,
} from "../shared/factories";
import { clone, extractAssistantText, getLastMessage } from "../shared/helpers";
import { getLastMessage } from "../shared/helpers";

// Mock the getTool function from tools module
const mockToolRegistry = new MockToolRegistry();
Expand Down
1 change: 0 additions & 1 deletion packages/tests/slash/handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { test, expect } from "bun:test";
import { handleSlashCommand } from "../../../commands/slash/handler";
import { registry } from "../../../commands/slash/registry";
import { registerCommands } from "../../../commands/slash/commands";
import type { SlashCommandContext } from "../../../commands/slash/types";

Expand Down
1 change: 0 additions & 1 deletion runtime/loop.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getTool, toolRegistry } from "../tools";
import { classifyCommand, commandOf, toolEffect } from "./toolEffects";
import { blockedInPlanMode, planModeRefusal, planModeTools } from "./planMode";
import { isRetryableError } from "./retry";
import { compactToolHistory, toolHistoryBudget } from "./compaction";
Expand Down
1 change: 0 additions & 1 deletion tools/runTests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Tool } from "../config/types";
import { store } from "../tui/src/store/ui-store";
import { formatCommandResult, runCommand } from "./command";
import { requestCommandApproval } from "./approval";

Expand Down
1 change: 0 additions & 1 deletion tools/terminal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Tool } from "../config/types";
import { store } from "../tui/src/store/ui-store";
import { formatCommandResult, runCommand } from "./command";
import { requestCommandApproval } from "./approval";

Expand Down
3 changes: 0 additions & 3 deletions tui/src/app.overlay.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { describe, expect, test, beforeEach } from "bun:test";
import React from "react";
import chalk from "chalk";
import { render } from "ink";
import { Writable } from "node:stream";
Expand Down Expand Up @@ -70,10 +69,8 @@ const homeScreen: HomeScreenData = {
subtitle: "AI software engineering agent",
promptExamples: ["Explain this repository"],
capabilities: ["Build"],
repository: "woop-code",
branch: "main",
providerName: "Gemini",
provider: "Gemini 2.5 Flash Lite",
};

const controller = {
Expand Down
3 changes: 0 additions & 3 deletions tui/src/app.planmode.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { describe, expect, test, beforeEach } from "bun:test";
import React from "react";
import chalk from "chalk";
import { render } from "ink";
import { Writable } from "node:stream";
Expand Down Expand Up @@ -96,10 +95,8 @@ const homeScreen: HomeScreenData = {
subtitle: "AI software engineering agent",
promptExamples: ["Explain this repository"],
capabilities: ["Build", "Plan"],
repository: "woop-code",
branch: "main",
providerName: "Gemini",
provider: "Gemini 2.5 Flash Lite",
};

/** Only the surface the composer touches, with the mode it actually holds. */
Expand Down
7 changes: 2 additions & 5 deletions tui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { ContinueTurn } from "./components/ContinueTurn";
import { QuestionDialog } from "./components/QuestionDialog";
import type { AgentController } from "../../commands/agentController";
import type { ActiveTurn, TimeLineItem } from "./types";
import { useEffect, useRef, useState, type ReactNode } from "react";
import { useEffect, useRef, useState } from "react";
import { useTerminalSize } from "./hooks/useTerminalSize";
import { useCancelKey } from "./hooks/useCancelKey";
import { planLayout } from "./layout";
Expand Down Expand Up @@ -116,7 +116,6 @@ export function App({ controller, onExit, homeScreen }: AppProps) {
) : (
<ConversationViewport
items={state.timeline}
isThinking={state.isThinking}
activeTurn={state.activeTurn}
scrollOffset={state.scrollOffset}
updateKey={state}
Expand Down Expand Up @@ -201,7 +200,6 @@ function ScrolledAwayHint() {

interface ConversationViewportProps {
items: TimeLineItem[];
isThinking: boolean;
activeTurn: ActiveTurn | null;
scrollOffset: number;
updateKey: object;
Expand All @@ -216,7 +214,6 @@ interface ConversationViewportProps {
*/
function ConversationViewport({
items,
isThinking,
activeTurn,
scrollOffset,
updateKey,
Expand Down Expand Up @@ -276,7 +273,7 @@ function ConversationViewport({
flexShrink={0}
marginBottom={-scrollOffset}
>
<Timeline items={items} isThinking={isThinking} activeTurn={activeTurn} />
<Timeline items={items} activeTurn={activeTurn} />
</Box>
</Box>
{/* This viewport is bottom-anchored — its offset counts up from the last
Expand Down
2 changes: 1 addition & 1 deletion tui/src/components/BootScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface BootScreenProps {
}

export function BootScreen({ onComplete }: BootScreenProps) {
const { logoText, loadingStep, doneSteps, phase } = useBootAnimation(onComplete);
const { loadingStep, doneSteps, phase } = useBootAnimation(onComplete);

return (
<Box flexDirection="column" paddingX={2} paddingY={1}>
Expand Down
5 changes: 0 additions & 5 deletions tui/src/components/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ export interface HomeScreenData {
subtitle: string;
promptExamples: readonly string[];
capabilities: readonly string[];
repository: string;
branch: string;
providerName: string;
provider: string;
}

export interface HomeScreenProps extends HomeScreenData {
Expand All @@ -35,9 +33,6 @@ export function HomeScreen({
subtitle,
promptExamples,
capabilities,
repository,
branch,
provider,
renderPrompt,
paletteOpen = false,
}: HomeScreenProps) {
Expand Down
2 changes: 1 addition & 1 deletion tui/src/components/MessageRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMemo } from "react";
import { Box, Text } from "ink";
import { Box } from "ink";
import type { Token } from "marked";
import { lexMarkdown } from "../markdown-lexer";
import { Markdown } from "./Markdown";
Expand Down
1 change: 0 additions & 1 deletion tui/src/components/PromptCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Box } from "ink";
import { useEffect, useState } from "react";
import type { ReactNode } from "react";
import { colors } from "../styles/theme";

const PLACEHOLDER_ROTATION_MS = 3500;
const TYPEWRITER_FRAME_MS = 24;
Expand Down
1 change: 0 additions & 1 deletion tui/src/components/TurnFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ function RunningTurnFooter(props: TurnFooterProps) {
function TurnFooterRow({
agent,
model,
endedAt,
outcome,
elapsed,
pulseFrame,
Expand Down
10 changes: 3 additions & 7 deletions tui/src/hooks/useBootAnimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import { useState, useEffect } from "react";
export type BootPhase = "logo" | "steps" | "launching" | "done";

export interface BootAnimationState {
logoText: string;
loadingStep: number; // index of the currently-spinning step (-1 = none)
doneSteps: Set<number>;
phase: BootPhase;
}

const LOGO = "Woopcode";
const STEPS = ["Runtime", "Provider", "Tool Registry", "Repository Context"];

const LOGO_DURATION_MS = 600; // duration of LogoReveal animation
Expand All @@ -18,10 +16,9 @@ const STEP_GAP_MS = 220; // gap between steps starting (must be > STEP_HOLD
const LAUNCH_MS = 350; // "Launching..." shown for this long before onComplete

export function useBootAnimation(onComplete: () => void): BootAnimationState {
const [logoText, setLogoText] = useState("");
const [loadingStep, setLoadingStep] = useState(-1);
const [doneSteps, setDoneSteps] = useState<Set<number>>(new Set());
const [phase, setPhase] = useState<BootPhase>("logo");
const [doneSteps, setDoneSteps] = useState<Set<number>>(new Set());
const [phase, setPhase] = useState<BootPhase>("logo");

useEffect(() => {
const timers: ReturnType<typeof setTimeout>[] = [];
Expand Down Expand Up @@ -58,10 +55,9 @@ export function useBootAnimation(onComplete: () => void): BootAnimationState {

return () => timers.forEach(clearTimeout);
// onComplete is stable (comes from useState setter reference in agent.tsx)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return { logoText, loadingStep, doneSteps, phase };
return { loadingStep, doneSteps, phase };
}

export { STEPS };
3 changes: 0 additions & 3 deletions tui/src/hooks/useCancelKey.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { describe, expect, test, beforeEach } from "bun:test";
import React from "react";
import { render } from "ink";
import { Writable } from "node:stream";
import { EventEmitter } from "node:events";
Expand Down Expand Up @@ -60,10 +59,8 @@ const homeScreen: HomeScreenData = {
subtitle: "AI software engineering agent",
promptExamples: ["Explain this repository"],
capabilities: ["Build"],
repository: "woop-code",
branch: "main",
providerName: "Gemini",
provider: "Gemini 2.5 Flash Lite",
};

function mountApp({ busy }: { busy: boolean }) {
Expand Down
1 change: 0 additions & 1 deletion tui/src/prompt.shape.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { describe, expect, test, beforeEach } from "bun:test";
import React from "react";
import chalk from "chalk";
import { render } from "ink";
import { Writable } from "node:stream";
Expand Down
3 changes: 1 addition & 2 deletions tui/src/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { TodoList } from "./components/TodoList";

interface TimelineProps {
items: TimeLineItem[];
isThinking: boolean;
activeTurn: ActiveTurn | null;
}

Expand All @@ -33,7 +32,7 @@ interface TimelineProps {
*/
const MAX_RENDERED_ITEMS = 300;

export function Timeline({ items, isThinking, activeTurn }: TimelineProps) {
export function Timeline({ items, activeTurn }: TimelineProps) {
return (
<Box flexDirection="column" flexShrink={0}>
<TimelineHistory items={items} />
Expand Down