diff --git a/src/constants/banner.ts b/src/constants/banner.ts index bafc10e..ebbe8a9 100644 --- a/src/constants/banner.ts +++ b/src/constants/banner.ts @@ -20,7 +20,6 @@ export const BANNER_MINIMAL = [ "╰───────────────────────────────────────╯", ] as const; -// Block-style banner (similar to opencode) export const BANNER_BLOCKS = [ "█▀▀ █▀█ █▀▄ █▀▀ ▀█▀ █▄█ █▀█ █▀▀ █▀█", "█ █ █ █ █ █▀▀ █ █ █▀▀ █▀▀ █▀▄", diff --git a/src/services/core/session.ts b/src/services/core/session.ts index 8e1034c..38f0451 100644 --- a/src/services/core/session.ts +++ b/src/services/core/session.ts @@ -264,7 +264,6 @@ export const setWorkingDirectory = async (dir: string): Promise => { /** * Create a subagent session (child of a parent session) - * Used by task_agent for proper session-based isolation like opencode */ export const createSubagentSession = async ( config: SubagentSessionConfig, @@ -283,8 +282,9 @@ export const createSubagentSession = async ( }; // Set working directory - (session as SubagentChatSession & { workingDirectory?: string }).workingDirectory = - config.workingDirectory; + ( + session as SubagentChatSession & { workingDirectory?: string } + ).workingDirectory = config.workingDirectory; // Save but don't set as current (subagents run independently) await saveSession(session); diff --git a/src/tools/task-agent/execute.ts b/src/tools/task-agent/execute.ts index 8bebd6b..c866928 100644 --- a/src/tools/task-agent/execute.ts +++ b/src/tools/task-agent/execute.ts @@ -2,7 +2,6 @@ * Task Agent Tool * * Allows spawning specialized sub-agents for complex tasks. - * Implements the agent delegation pattern from claude-code and opencode. * Supports parallel execution of up to 3 agents simultaneously. */ @@ -63,9 +62,7 @@ const taskAgentSchema = z.object({ .enum(["explore", "implement", "test", "review", "refactor", "plan"]) .describe("The type of specialized agent to spawn"), - task: z - .string() - .describe("The task for the agent to perform"), + task: z.string().describe("The task for the agent to perform"), context_files: z .array(z.string()) @@ -124,13 +121,21 @@ const agentQueue: QueuedAgent[] = []; * Process the agent queue */ const processQueue = async (): Promise => { - while (agentQueue.length > 0 && runningForegroundAgents < MAX_CONCURRENT_AGENTS) { + while ( + agentQueue.length > 0 && + runningForegroundAgents < MAX_CONCURRENT_AGENTS + ) { const queued = agentQueue.shift(); if (!queued) break; runningForegroundAgents++; - executeAgentInternal(queued.params, queued.systemPrompt, queued.taskPrompt, queued.ctx) + executeAgentInternal( + queued.params, + queued.systemPrompt, + queued.taskPrompt, + queued.ctx, + ) .then(queued.resolve) .catch(queued.reject) .finally(() => { @@ -167,7 +172,7 @@ export const executeTaskAgent = async ( */ const buildAgentSystemPrompt = ( agentType: AgentType, - config: typeof AGENT_TYPES[AgentType], + config: (typeof AGENT_TYPES)[AgentType], ): string => { const prompts: Record = { explore: `You are an EXPLORATION agent. Your task is to quickly understand code. @@ -267,7 +272,9 @@ const buildTaskPrompt = (params: TaskAgentParams): string => { const parts = [`## Task\n${params.task}`]; if (params.context_files?.length) { - parts.push(`\n## Context Files\n${params.context_files.map(f => `- ${f}`).join("\n")}`); + parts.push( + `\n## Context Files\n${params.context_files.map((f) => `- ${f}`).join("\n")}`, + ); } return parts.join("\n"); @@ -429,7 +436,7 @@ export const getBackgroundAgentStatus = async ( // Check if completed (with short timeout) const result = await Promise.race([ - agent.promise.then(r => ({ completed: true as const, result: r })), + agent.promise.then((r) => ({ completed: true as const, result: r })), new Promise<{ completed: false }>((resolve) => setTimeout(() => resolve({ completed: false }), 100), ), diff --git a/src/tui-solid/app.tsx b/src/tui-solid/app.tsx index d1031be..186c6c0 100644 --- a/src/tui-solid/app.tsx +++ b/src/tui-solid/app.tsx @@ -20,11 +20,13 @@ import { matchesAction } from "@services/keybind-resolver"; import { copyToClipboard } from "@services/clipboard/text-clipboard"; import versionData from "@/version.json"; import { ExitProvider, useExit } from "@tui-solid/context/exit"; +import { generateSessionSummary } from "@utils/core/session-stats"; import { RouteProvider, useRoute } from "@tui-solid/context/route"; import { AppStoreProvider, useAppStore, setAppStoreRef, + appStore, } from "@tui-solid/context/app"; import { ThemeProvider, useTheme } from "@tui-solid/context/theme"; import { KeybindProvider } from "@tui-solid/context/keybind"; @@ -116,6 +118,20 @@ function AppContent(props: AppProps) { setAppStoreRef(app); + // Set exit message reactively (like OpenCode does) + // This ensures the message is pre-computed and ready when exit is called + createEffect(() => { + const state = appStore.getState(); + const summary = generateSessionSummary({ + sessionId: props.sessionId ?? "unknown", + sessionStats: state.sessionStats, + modifiedFiles: state.modifiedFiles, + modelName: state.model, + providerName: state.provider, + }); + exit.setExitMessage(summary); + }); + /** Copy selected text to clipboard and clear selection */ const copySelectionToClipboard = async (): Promise => { const text = renderer.getSelection()?.getSelectedText(); @@ -518,7 +534,6 @@ function App(props: AppProps) { return ( }> props.onExit({ exitCode: 0, sessionId: props.sessionId })} > diff --git a/src/tui-solid/context/exit.tsx b/src/tui-solid/context/exit.tsx index 97d10e9..84c6470 100644 --- a/src/tui-solid/context/exit.tsx +++ b/src/tui-solid/context/exit.tsx @@ -1,12 +1,9 @@ import { createSignal, onCleanup } from "solid-js"; import { useRenderer } from "@opentui/solid"; import { createSimpleContext } from "./helper"; -import { generateSessionSummary } from "@utils/core/session-stats"; -import { appStore } from "./app"; interface ExitContextInput extends Record { - sessionId?: string; - onExit?: () => void; + onExit?: () => void | Promise; } interface ExitContextValue { @@ -16,6 +13,8 @@ interface ExitContextValue { requestExit: () => void; cancelExit: () => void; confirmExit: () => void; + setExitMessage: (message: string | undefined) => void; + getExitMessage: () => string | undefined; } export const { provider: ExitProvider, use: useExit } = createSimpleContext< @@ -28,34 +27,43 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext< const [exitCode, setExitCode] = createSignal(0); const [isExiting, setIsExiting] = createSignal(false); const [exitRequested, setExitRequested] = createSignal(false); + const [exitMessage, setExitMessageState] = createSignal(undefined); - const exit = (code = 0): void => { + const exit = async (code = 0): Promise => { setExitCode(code); setIsExiting(true); - // Generate and store exit message before destroying renderer - const state = appStore.getState(); - const summary = generateSessionSummary({ - sessionId: props.sessionId ?? "unknown", - sessionStats: state.sessionStats, - modifiedFiles: state.modifiedFiles, - modelName: state.model, - providerName: state.provider, - }); + // Reset window title before destroying renderer + try { + renderer.setTerminalTitle(""); + } catch { + // Ignore + } - // Destroy renderer first to stop rendering + // Destroy renderer to stop rendering and exit alternate screen renderer.destroy(); - // Call the onExit callback - props.onExit?.(); + // Call the onExit callback (may be async) + await props.onExit?.(); - // Write the summary after renderer is destroyed - process.stdout.write(summary + "\n"); + // Write the stored exit message after renderer is destroyed + const message = exitMessage(); + if (message) { + process.stdout.write(message + "\n"); + } // Exit the process process.exit(code); }; + const setExitMessage = (message: string | undefined): void => { + setExitMessageState(message); + }; + + const getExitMessage = (): string | undefined => { + return exitMessage(); + }; + const requestExit = (): void => { setExitRequested(true); }; @@ -73,6 +81,7 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext< onCleanup(() => { setIsExiting(false); setExitRequested(false); + setExitMessageState(undefined); }); return { @@ -82,6 +91,8 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext< requestExit, cancelExit, confirmExit, + setExitMessage, + getExitMessage, }; }, });