sparkecoder 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agent/index.ts","../../src/db/index.ts","../../src/db/schema.ts","../../src/config/index.ts","../../src/config/types.ts","../../src/tools/bash.ts","../../src/utils/truncate.ts","../../src/terminal/tmux.ts","../../src/tools/read-file.ts","../../src/tools/write-file.ts","../../src/tools/todo.ts","../../src/tools/load-skill.ts","../../src/skills/index.ts","../../src/tools/index.ts","../../src/agent/context.ts","../../src/agent/prompts.ts"],"sourcesContent":["import {\n streamText,\n generateText,\n tool,\n stepCountIs,\n type ToolSet,\n type ModelMessage,\n} from 'ai';\nimport { gateway } from '@ai-sdk/gateway';\nimport { z } from 'zod';\nimport { nanoid } from 'nanoid';\nimport {\n sessionQueries,\n toolExecutionQueries,\n Session,\n ToolExecution,\n} from '../db/index.js';\nimport { getConfig, requiresApproval, SessionConfig } from '../config/index.js';\nimport { createTools, BashToolProgress } from '../tools/index.js';\nimport { ContextManager } from './context.js';\nimport { buildSystemPrompt } from './prompts.js';\n\n// Shared store for approval resolvers (needed because approve/reject come from different HTTP requests)\nconst approvalResolvers = new Map<string, { \n resolve: (approved: boolean) => void; \n reason?: string;\n sessionId: string;\n}>();\n\nexport interface AgentOptions {\n sessionId?: string;\n name?: string;\n workingDirectory?: string;\n model?: string;\n sessionConfig?: Partial<SessionConfig>;\n}\n\nexport interface AgentRunOptions {\n prompt: string;\n abortSignal?: AbortSignal;\n /** Skip saving user message (if already saved externally) */\n skipSaveUserMessage?: boolean;\n onText?: (text: string) => void;\n onToolCall?: (toolCall: { toolCallId: string; toolName: string; input: unknown }) => void;\n onToolResult?: (result: { toolCallId: string; toolName: string; output: unknown }) => void;\n onApprovalRequired?: (execution: ToolExecution) => void;\n onStepFinish?: (step: { text?: string; toolCalls?: unknown[]; usage?: unknown }) => void;\n onAbort?: (info: { steps: unknown[] }) => void;\n /** Called when a tool (like bash) has progress to report (e.g., terminal started) */\n onToolProgress?: (progress: { toolName: string; data: BashToolProgress }) => void;\n}\n\nexport interface AgentStreamResult {\n sessionId: string;\n stream: ReturnType<typeof streamText>;\n waitForApprovals: () => Promise<ToolExecution[]>;\n /** Call this after stream completes to save response messages */\n saveResponseMessages: () => Promise<void>;\n}\n\n/**\n * The main coding agent that orchestrates LLM interactions\n */\nexport class Agent {\n private session: Session;\n private context: ContextManager;\n private baseTools: ToolSet;\n private pendingApprovals: Map<string, ToolExecution> = new Map();\n\n private constructor(session: Session, context: ContextManager, tools: ToolSet) {\n this.session = session;\n this.context = context;\n this.baseTools = tools;\n }\n\n /**\n * Create tools with optional progress callbacks\n */\n private createToolsWithCallbacks(options: {\n onToolProgress?: AgentRunOptions['onToolProgress'];\n }): ToolSet {\n const config = getConfig();\n return createTools({\n sessionId: this.session.id,\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n onBashProgress: options.onToolProgress\n ? (progress) => options.onToolProgress!({ toolName: 'bash', data: progress })\n : undefined,\n });\n }\n\n /**\n * Create or resume an agent session\n */\n static async create(options: AgentOptions = {}): Promise<Agent> {\n const config = getConfig();\n\n // Get or create session\n let session: Session;\n\n if (options.sessionId) {\n const existing = sessionQueries.getById(options.sessionId);\n if (!existing) {\n throw new Error(`Session not found: ${options.sessionId}`);\n }\n session = existing;\n } else {\n session = sessionQueries.create({\n name: options.name,\n workingDirectory: options.workingDirectory || config.resolvedWorkingDirectory,\n model: options.model || config.defaultModel,\n config: options.sessionConfig as SessionConfig,\n });\n }\n\n // Create context manager\n const context = new ContextManager({\n sessionId: session.id,\n maxContextChars: config.context?.maxChars || 200_000,\n keepRecentMessages: config.context?.keepRecentMessages || 10,\n autoSummarize: config.context?.autoSummarize ?? true,\n });\n\n // Create tools\n const tools = createTools({\n sessionId: session.id,\n workingDirectory: session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n });\n\n return new Agent(session, context, tools);\n }\n\n /**\n * Get the session ID\n */\n get sessionId(): string {\n return this.session.id;\n }\n\n /**\n * Get session details\n */\n getSession(): Session {\n return this.session;\n }\n\n /**\n * Run the agent with a prompt (streaming)\n */\n async stream(options: AgentRunOptions): Promise<AgentStreamResult> {\n const config = getConfig();\n\n // Add user message to context (skip if already saved externally)\n if (!options.skipSaveUserMessage) {\n this.context.addUserMessage(options.prompt);\n }\n\n // Update session status\n sessionQueries.updateStatus(this.session.id, 'active');\n\n // Build system prompt\n const systemPrompt = await buildSystemPrompt({\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n sessionId: this.session.id,\n });\n\n // Get conversation history\n const messages = await this.context.getMessages();\n\n // Create tools with progress callbacks if needed\n const tools = options.onToolProgress\n ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress })\n : this.baseTools;\n\n // Wrap tools with approval checking\n const wrappedTools = this.wrapToolsWithApproval(options, tools);\n\n // Create stream with reasoning enabled for supported models\n const stream = streamText({\n model: gateway(this.session.model) as any,\n system: systemPrompt,\n messages: messages as any,\n tools: wrappedTools,\n stopWhen: stepCountIs(500),\n // Forward abort signal if provided\n abortSignal: options.abortSignal,\n // Enable extended thinking/reasoning for models that support it\n providerOptions: {\n anthropic: {\n thinking: {\n type: 'enabled',\n budgetTokens: 10000,\n },\n },\n },\n onStepFinish: async (step) => {\n options.onStepFinish?.(step as any);\n },\n onAbort: ({ steps }) => {\n options.onAbort?.({ steps });\n },\n });\n\n // Helper to save response messages after stream completes\n const saveResponseMessages = async () => {\n const result = await stream;\n const response = await result.response;\n const responseMessages = response.messages as ModelMessage[];\n this.context.addResponseMessages(responseMessages);\n };\n\n return {\n sessionId: this.session.id,\n stream,\n waitForApprovals: () => this.waitForApprovals(),\n saveResponseMessages,\n };\n }\n\n /**\n * Run the agent with a prompt (non-streaming)\n */\n async run(options: Omit<AgentRunOptions, 'onText'>): Promise<{ text: string; steps: unknown[] }> {\n const config = getConfig();\n\n // Add user message to context\n this.context.addUserMessage(options.prompt);\n\n // Build system prompt\n const systemPrompt = await buildSystemPrompt({\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n sessionId: this.session.id,\n });\n\n // Get conversation history\n const messages = await this.context.getMessages();\n\n // Create tools with progress callbacks if needed\n const tools = options.onToolProgress\n ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress })\n : this.baseTools;\n\n // Wrap tools with approval checking\n const wrappedTools = this.wrapToolsWithApproval(options, tools);\n\n const result = await generateText({\n model: gateway(this.session.model) as any,\n system: systemPrompt,\n messages: messages as any,\n tools: wrappedTools,\n stopWhen: stepCountIs(500),\n // Enable extended thinking/reasoning for models that support it\n providerOptions: {\n anthropic: {\n thinking: {\n type: 'enabled',\n budgetTokens: 10000,\n },\n },\n },\n });\n\n // Save response messages using the proper AI SDK format\n const responseMessages = result.response.messages as ModelMessage[];\n this.context.addResponseMessages(responseMessages);\n\n return {\n text: result.text,\n steps: result.steps,\n };\n }\n\n /**\n * Wrap tools to add approval checking\n */\n private wrapToolsWithApproval(options: AgentRunOptions, tools?: ToolSet): ToolSet {\n const sessionConfig = this.session.config;\n const wrappedTools: ToolSet = {};\n const toolsToWrap = tools || this.baseTools;\n\n for (const [name, originalTool] of Object.entries(toolsToWrap)) {\n const needsApproval = requiresApproval(name, sessionConfig ?? undefined);\n\n if (!needsApproval) {\n wrappedTools[name] = originalTool;\n continue;\n }\n\n // Create wrapped tool that checks for approval and waits\n wrappedTools[name] = tool({\n description: originalTool.description || '',\n inputSchema: (originalTool as any).inputSchema || z.object({}),\n execute: async (input: unknown, toolOptions: { toolCallId?: string }) => {\n const toolCallId = toolOptions.toolCallId || nanoid();\n\n // Record the execution\n const execution = toolExecutionQueries.create({\n sessionId: this.session.id,\n toolName: name,\n toolCallId,\n input: input as any,\n requiresApproval: true,\n status: 'pending',\n });\n\n // Store pending approval\n this.pendingApprovals.set(toolCallId, execution);\n\n // Notify about approval requirement\n options.onApprovalRequired?.(execution);\n\n // Update session status\n sessionQueries.updateStatus(this.session.id, 'waiting');\n\n // Wait for approval decision (using shared store for cross-request access)\n const approved = await new Promise<boolean>((resolve) => {\n approvalResolvers.set(toolCallId, { resolve, sessionId: this.session.id });\n });\n\n // Get any rejection reason\n const resolverData = approvalResolvers.get(toolCallId);\n approvalResolvers.delete(toolCallId);\n this.pendingApprovals.delete(toolCallId);\n\n if (!approved) {\n // Tool was rejected\n const reason = resolverData?.reason || 'User rejected the tool execution';\n toolExecutionQueries.reject(execution.id);\n sessionQueries.updateStatus(this.session.id, 'active');\n \n return {\n status: 'rejected',\n toolCallId,\n rejected: true,\n reason,\n message: `Tool \"${name}\" was rejected by the user. Reason: ${reason}`,\n };\n }\n\n // Tool was approved - execute the original tool\n toolExecutionQueries.approve(execution.id);\n sessionQueries.updateStatus(this.session.id, 'active');\n\n try {\n const result = await (originalTool as any).execute(input, toolOptions);\n toolExecutionQueries.complete(execution.id, result);\n return result;\n } catch (error: any) {\n toolExecutionQueries.complete(execution.id, null, error.message);\n throw error;\n }\n },\n });\n }\n\n return wrappedTools;\n }\n\n /**\n * Wait for all pending approvals\n */\n async waitForApprovals(): Promise<ToolExecution[]> {\n return Array.from(this.pendingApprovals.values());\n }\n\n /**\n * Approve a pending tool execution\n */\n async approve(toolCallId: string): Promise<{ approved: true }> {\n // Check shared resolver store (the streaming Agent is waiting on this)\n const resolver = approvalResolvers.get(toolCallId);\n if (resolver) {\n resolver.resolve(true);\n return { approved: true };\n }\n\n // Fall back to database lookup\n const pendingFromDb = toolExecutionQueries.getPendingApprovals(this.session.id);\n const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);\n \n if (!execution) {\n throw new Error(`No pending approval for tool call: ${toolCallId}`);\n }\n\n // Mark as approved in DB\n toolExecutionQueries.approve(execution.id);\n return { approved: true };\n }\n\n /**\n * Reject a pending tool execution\n */\n reject(toolCallId: string, reason?: string): { rejected: true } {\n // Check shared resolver store (the streaming Agent is waiting on this)\n const resolver = approvalResolvers.get(toolCallId);\n if (resolver) {\n resolver.reason = reason;\n resolver.resolve(false);\n return { rejected: true };\n }\n\n // Fall back to database lookup\n const pendingFromDb = toolExecutionQueries.getPendingApprovals(this.session.id);\n const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);\n \n if (!execution) {\n throw new Error(`No pending approval for tool call: ${toolCallId}`);\n }\n\n // Mark as rejected in DB\n toolExecutionQueries.reject(execution.id);\n return { rejected: true };\n }\n\n /**\n * Get pending approvals\n */\n getPendingApprovals(): ToolExecution[] {\n return toolExecutionQueries.getPendingApprovals(this.session.id);\n }\n\n /**\n * Get context statistics\n */\n getContextStats() {\n return this.context.getStats();\n }\n\n /**\n * Clear conversation context (start fresh)\n */\n clearContext(): void {\n this.context.clear();\n }\n}\n\nexport { ContextManager } from './context.js';\nexport { buildSystemPrompt } from './prompts.js';\n","import Database from 'better-sqlite3';\nimport { drizzle } from 'drizzle-orm/better-sqlite3';\nimport { eq, desc, and, sql } from 'drizzle-orm';\nimport { nanoid } from 'nanoid';\nimport * as schema from './schema.js';\nimport type {\n Session,\n NewSession,\n Message,\n NewMessage,\n ToolExecution,\n NewToolExecution,\n TodoItem,\n NewTodoItem,\n SessionConfig,\n ModelMessage,\n Terminal,\n NewTerminal,\n ActiveStream,\n NewActiveStream,\n} from './schema.js';\n\nlet db: ReturnType<typeof drizzle<typeof schema>> | null = null;\nlet sqlite: Database.Database | null = null;\n\nexport function initDatabase(dbPath: string) {\n sqlite = new Database(dbPath);\n sqlite.pragma('journal_mode = WAL');\n db = drizzle(sqlite, { schema });\n\n // Create tables if they don't exist (preserves existing data)\n sqlite.exec(`\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n name TEXT,\n working_directory TEXT NOT NULL,\n model TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'active',\n config TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n model_message TEXT NOT NULL,\n sequence INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS tool_executions (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,\n tool_name TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n input TEXT,\n output TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n requires_approval INTEGER NOT NULL DEFAULT 0,\n error TEXT,\n started_at INTEGER NOT NULL,\n completed_at INTEGER\n );\n\n CREATE TABLE IF NOT EXISTS todo_items (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n content TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n \"order\" INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS loaded_skills (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n skill_name TEXT NOT NULL,\n loaded_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS terminals (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n name TEXT,\n command TEXT NOT NULL,\n cwd TEXT NOT NULL,\n pid INTEGER,\n status TEXT NOT NULL DEFAULT 'running',\n exit_code INTEGER,\n error TEXT,\n created_at INTEGER NOT NULL,\n stopped_at INTEGER\n );\n\n -- Table for tracking active streams (for resumable streams)\n CREATE TABLE IF NOT EXISTS active_streams (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n stream_id TEXT NOT NULL UNIQUE,\n status TEXT NOT NULL DEFAULT 'active',\n created_at INTEGER NOT NULL,\n finished_at INTEGER\n );\n\n CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);\n CREATE INDEX IF NOT EXISTS idx_tool_executions_session ON tool_executions(session_id);\n CREATE INDEX IF NOT EXISTS idx_todo_items_session ON todo_items(session_id);\n CREATE INDEX IF NOT EXISTS idx_loaded_skills_session ON loaded_skills(session_id);\n CREATE INDEX IF NOT EXISTS idx_terminals_session ON terminals(session_id);\n CREATE INDEX IF NOT EXISTS idx_active_streams_session ON active_streams(session_id);\n `);\n\n return db;\n}\n\nexport function getDb() {\n if (!db) {\n throw new Error('Database not initialized. Call initDatabase first.');\n }\n return db;\n}\n\nexport function closeDatabase() {\n if (sqlite) {\n sqlite.close();\n sqlite = null;\n db = null;\n }\n}\n\n// Session queries\nexport const sessionQueries = {\n create(data: Omit<NewSession, 'id' | 'createdAt' | 'updatedAt'>): Session {\n const id = nanoid();\n const now = new Date();\n const result = getDb()\n .insert(schema.sessions)\n .values({\n id,\n ...data,\n createdAt: now,\n updatedAt: now,\n })\n .returning()\n .get();\n return result;\n },\n\n getById(id: string): Session | undefined {\n return getDb().select().from(schema.sessions).where(eq(schema.sessions.id, id)).get();\n },\n\n list(limit = 50, offset = 0): Session[] {\n return getDb()\n .select()\n .from(schema.sessions)\n .orderBy(desc(schema.sessions.createdAt))\n .limit(limit)\n .offset(offset)\n .all();\n },\n\n updateStatus(id: string, status: Session['status']): Session | undefined {\n return getDb()\n .update(schema.sessions)\n .set({ status, updatedAt: new Date() })\n .where(eq(schema.sessions.id, id))\n .returning()\n .get();\n },\n\n updateModel(id: string, model: string): Session | undefined {\n return getDb()\n .update(schema.sessions)\n .set({ model, updatedAt: new Date() })\n .where(eq(schema.sessions.id, id))\n .returning()\n .get();\n },\n\n update(id: string, updates: { model?: string; name?: string; config?: SessionConfig }): Session | undefined {\n return getDb()\n .update(schema.sessions)\n .set({ ...updates, updatedAt: new Date() })\n .where(eq(schema.sessions.id, id))\n .returning()\n .get();\n },\n\n delete(id: string): boolean {\n const result = getDb().delete(schema.sessions).where(eq(schema.sessions.id, id)).run();\n return result.changes > 0;\n },\n};\n\n// Message queries - stores AI SDK ModelMessage directly\nexport const messageQueries = {\n /**\n * Get the next sequence number for a session\n */\n getNextSequence(sessionId: string): number {\n const result = getDb()\n .select({ maxSeq: sql<number>`COALESCE(MAX(sequence), -1)` })\n .from(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .get();\n return (result?.maxSeq ?? -1) + 1;\n },\n\n /**\n * Create a single message from a ModelMessage\n */\n create(sessionId: string, modelMessage: ModelMessage): Message {\n const id = nanoid();\n const sequence = this.getNextSequence(sessionId);\n const result = getDb()\n .insert(schema.messages)\n .values({\n id,\n sessionId,\n modelMessage,\n sequence,\n createdAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n /**\n * Add multiple ModelMessages at once (from response.messages)\n * Maintains insertion order via sequence numbers\n */\n addMany(sessionId: string, modelMessages: ModelMessage[]): Message[] {\n const results: Message[] = [];\n let sequence = this.getNextSequence(sessionId);\n for (const msg of modelMessages) {\n const id = nanoid();\n const result = getDb()\n .insert(schema.messages)\n .values({\n id,\n sessionId,\n modelMessage: msg,\n sequence,\n createdAt: new Date(),\n })\n .returning()\n .get();\n results.push(result);\n sequence++;\n }\n return results;\n },\n\n /**\n * Get all messages for a session as ModelMessage[]\n * Ordered by sequence to maintain exact insertion order\n */\n getBySession(sessionId: string): Message[] {\n return getDb()\n .select()\n .from(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .orderBy(schema.messages.sequence)\n .all();\n },\n\n /**\n * Get ModelMessages directly (for passing to AI SDK)\n */\n getModelMessages(sessionId: string): ModelMessage[] {\n const messages = this.getBySession(sessionId);\n return messages.map(m => m.modelMessage);\n },\n\n getRecentBySession(sessionId: string, limit = 50): Message[] {\n return getDb()\n .select()\n .from(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .orderBy(desc(schema.messages.sequence))\n .limit(limit)\n .all()\n .reverse();\n },\n\n countBySession(sessionId: string): number {\n const result = getDb()\n .select({ count: sql<number>`count(*)` })\n .from(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .get();\n return result?.count ?? 0;\n },\n\n deleteBySession(sessionId: string): number {\n const result = getDb()\n .delete(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\n// Tool execution queries\nexport const toolExecutionQueries = {\n create(data: Omit<NewToolExecution, 'id' | 'startedAt'>): ToolExecution {\n const id = nanoid();\n const result = getDb()\n .insert(schema.toolExecutions)\n .values({\n id,\n ...data,\n startedAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getById(id: string): ToolExecution | undefined {\n return getDb()\n .select()\n .from(schema.toolExecutions)\n .where(eq(schema.toolExecutions.id, id))\n .get();\n },\n\n getByToolCallId(toolCallId: string): ToolExecution | undefined {\n return getDb()\n .select()\n .from(schema.toolExecutions)\n .where(eq(schema.toolExecutions.toolCallId, toolCallId))\n .get();\n },\n\n getPendingApprovals(sessionId: string): ToolExecution[] {\n return getDb()\n .select()\n .from(schema.toolExecutions)\n .where(\n and(\n eq(schema.toolExecutions.sessionId, sessionId),\n eq(schema.toolExecutions.status, 'pending'),\n eq(schema.toolExecutions.requiresApproval, true)\n )\n )\n .all();\n },\n\n approve(id: string): ToolExecution | undefined {\n return getDb()\n .update(schema.toolExecutions)\n .set({ status: 'approved' })\n .where(eq(schema.toolExecutions.id, id))\n .returning()\n .get();\n },\n\n reject(id: string): ToolExecution | undefined {\n return getDb()\n .update(schema.toolExecutions)\n .set({ status: 'rejected' })\n .where(eq(schema.toolExecutions.id, id))\n .returning()\n .get();\n },\n\n complete(\n id: string,\n output: unknown,\n error?: string\n ): ToolExecution | undefined {\n return getDb()\n .update(schema.toolExecutions)\n .set({\n status: error ? 'error' : 'completed',\n output: output as any,\n error,\n completedAt: new Date(),\n })\n .where(eq(schema.toolExecutions.id, id))\n .returning()\n .get();\n },\n\n getBySession(sessionId: string): ToolExecution[] {\n return getDb()\n .select()\n .from(schema.toolExecutions)\n .where(eq(schema.toolExecutions.sessionId, sessionId))\n .orderBy(schema.toolExecutions.startedAt)\n .all();\n },\n};\n\n// Todo item queries\nexport const todoQueries = {\n create(data: Omit<NewTodoItem, 'id' | 'createdAt' | 'updatedAt'>): TodoItem {\n const id = nanoid();\n const now = new Date();\n const result = getDb()\n .insert(schema.todoItems)\n .values({\n id,\n ...data,\n createdAt: now,\n updatedAt: now,\n })\n .returning()\n .get();\n return result;\n },\n\n createMany(\n sessionId: string,\n items: Array<{ content: string; order?: number }>\n ): TodoItem[] {\n const now = new Date();\n const values = items.map((item, index) => ({\n id: nanoid(),\n sessionId,\n content: item.content,\n order: item.order ?? index,\n createdAt: now,\n updatedAt: now,\n }));\n\n return getDb().insert(schema.todoItems).values(values).returning().all();\n },\n\n getBySession(sessionId: string): TodoItem[] {\n return getDb()\n .select()\n .from(schema.todoItems)\n .where(eq(schema.todoItems.sessionId, sessionId))\n .orderBy(schema.todoItems.order)\n .all();\n },\n\n updateStatus(\n id: string,\n status: TodoItem['status']\n ): TodoItem | undefined {\n return getDb()\n .update(schema.todoItems)\n .set({ status, updatedAt: new Date() })\n .where(eq(schema.todoItems.id, id))\n .returning()\n .get();\n },\n\n delete(id: string): boolean {\n const result = getDb()\n .delete(schema.todoItems)\n .where(eq(schema.todoItems.id, id))\n .run();\n return result.changes > 0;\n },\n\n clearSession(sessionId: string): number {\n const result = getDb()\n .delete(schema.todoItems)\n .where(eq(schema.todoItems.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\n// Loaded skills queries\nexport const skillQueries = {\n load(sessionId: string, skillName: string): schema.LoadedSkill {\n const id = nanoid();\n const result = getDb()\n .insert(schema.loadedSkills)\n .values({\n id,\n sessionId,\n skillName,\n loadedAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getBySession(sessionId: string): schema.LoadedSkill[] {\n return getDb()\n .select()\n .from(schema.loadedSkills)\n .where(eq(schema.loadedSkills.sessionId, sessionId))\n .orderBy(schema.loadedSkills.loadedAt)\n .all();\n },\n\n isLoaded(sessionId: string, skillName: string): boolean {\n const result = getDb()\n .select()\n .from(schema.loadedSkills)\n .where(\n and(\n eq(schema.loadedSkills.sessionId, sessionId),\n eq(schema.loadedSkills.skillName, skillName)\n )\n )\n .get();\n return !!result;\n },\n};\n\n// Terminal queries\nexport const terminalQueries = {\n create(data: Omit<NewTerminal, 'id' | 'createdAt'>): Terminal {\n const id = nanoid();\n const result = getDb()\n .insert(schema.terminals)\n .values({\n id,\n ...data,\n createdAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getById(id: string): Terminal | undefined {\n return getDb()\n .select()\n .from(schema.terminals)\n .where(eq(schema.terminals.id, id))\n .get();\n },\n\n getBySession(sessionId: string): Terminal[] {\n return getDb()\n .select()\n .from(schema.terminals)\n .where(eq(schema.terminals.sessionId, sessionId))\n .orderBy(desc(schema.terminals.createdAt))\n .all();\n },\n\n getRunning(sessionId: string): Terminal[] {\n return getDb()\n .select()\n .from(schema.terminals)\n .where(\n and(\n eq(schema.terminals.sessionId, sessionId),\n eq(schema.terminals.status, 'running')\n )\n )\n .all();\n },\n\n updateStatus(\n id: string,\n status: Terminal['status'],\n exitCode?: number,\n error?: string\n ): Terminal | undefined {\n return getDb()\n .update(schema.terminals)\n .set({\n status,\n exitCode,\n error,\n stoppedAt: status !== 'running' ? new Date() : undefined,\n })\n .where(eq(schema.terminals.id, id))\n .returning()\n .get();\n },\n\n updatePid(id: string, pid: number): Terminal | undefined {\n return getDb()\n .update(schema.terminals)\n .set({ pid })\n .where(eq(schema.terminals.id, id))\n .returning()\n .get();\n },\n\n delete(id: string): boolean {\n const result = getDb()\n .delete(schema.terminals)\n .where(eq(schema.terminals.id, id))\n .run();\n return result.changes > 0;\n },\n\n deleteBySession(sessionId: string): number {\n const result = getDb()\n .delete(schema.terminals)\n .where(eq(schema.terminals.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\n// Active stream queries - for resumable streams\nexport const activeStreamQueries = {\n create(sessionId: string, streamId: string): ActiveStream {\n const id = nanoid();\n const result = getDb()\n .insert(schema.activeStreams)\n .values({\n id,\n sessionId,\n streamId,\n status: 'active',\n createdAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getBySessionId(sessionId: string): ActiveStream | undefined {\n return getDb()\n .select()\n .from(schema.activeStreams)\n .where(\n and(\n eq(schema.activeStreams.sessionId, sessionId),\n eq(schema.activeStreams.status, 'active')\n )\n )\n .get();\n },\n\n getByStreamId(streamId: string): ActiveStream | undefined {\n return getDb()\n .select()\n .from(schema.activeStreams)\n .where(eq(schema.activeStreams.streamId, streamId))\n .get();\n },\n\n finish(streamId: string): ActiveStream | undefined {\n return getDb()\n .update(schema.activeStreams)\n .set({ status: 'finished', finishedAt: new Date() })\n .where(eq(schema.activeStreams.streamId, streamId))\n .returning()\n .get();\n },\n\n markError(streamId: string): ActiveStream | undefined {\n return getDb()\n .update(schema.activeStreams)\n .set({ status: 'error', finishedAt: new Date() })\n .where(eq(schema.activeStreams.streamId, streamId))\n .returning()\n .get();\n },\n\n deleteBySession(sessionId: string): number {\n const result = getDb()\n .delete(schema.activeStreams)\n .where(eq(schema.activeStreams.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\nexport type { \n Session, \n Message, \n ToolExecution, \n TodoItem, \n SessionConfig, \n ModelMessage,\n Terminal,\n ActiveStream,\n};\n","import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';\n\n// Sessions table - represents an agent session/thread\nexport const sessions = sqliteTable('sessions', {\n id: text('id').primaryKey(),\n name: text('name'),\n workingDirectory: text('working_directory').notNull(),\n model: text('model').notNull(),\n status: text('status', { enum: ['active', 'waiting', 'completed', 'error'] })\n .notNull()\n .default('active'),\n config: text('config', { mode: 'json' }).$type<SessionConfig>(),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n updatedAt: integer('updated_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Messages table - stores AI SDK ModelMessage directly\nexport const messages = sqliteTable('messages', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n // Store the entire ModelMessage as JSON (role + content)\n modelMessage: text('model_message', { mode: 'json' }).$type<ModelMessage>().notNull(),\n // Sequence number within session to maintain exact ordering\n sequence: integer('sequence').notNull().default(0),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Tool executions - tracks all tool calls and their results\nexport const toolExecutions = sqliteTable('tool_executions', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n messageId: text('message_id').references(() => messages.id, { onDelete: 'cascade' }),\n toolName: text('tool_name').notNull(),\n toolCallId: text('tool_call_id').notNull(),\n input: text('input', { mode: 'json' }),\n output: text('output', { mode: 'json' }),\n status: text('status', { enum: ['pending', 'approved', 'rejected', 'completed', 'error'] })\n .notNull()\n .default('pending'),\n requiresApproval: integer('requires_approval', { mode: 'boolean' }).notNull().default(false),\n error: text('error'),\n startedAt: integer('started_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n completedAt: integer('completed_at', { mode: 'timestamp' }),\n});\n\n// Todo items for the planning tool\nexport const todoItems = sqliteTable('todo_items', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n content: text('content').notNull(),\n status: text('status', { enum: ['pending', 'in_progress', 'completed', 'cancelled'] })\n .notNull()\n .default('pending'),\n order: integer('order').notNull().default(0),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n updatedAt: integer('updated_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Skills loaded into sessions\nexport const loadedSkills = sqliteTable('loaded_skills', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n skillName: text('skill_name').notNull(),\n loadedAt: integer('loaded_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Terminal sessions - background processes managed by agents\nexport const terminals = sqliteTable('terminals', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n name: text('name'), // Optional friendly name (e.g., \"dev-server\")\n command: text('command').notNull(), // The command that was run\n cwd: text('cwd').notNull(), // Working directory\n pid: integer('pid'), // Process ID (null if not running)\n status: text('status', { enum: ['running', 'stopped', 'error'] })\n .notNull()\n .default('running'),\n exitCode: integer('exit_code'), // Exit code if stopped\n error: text('error'), // Error message if status is 'error'\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n stoppedAt: integer('stopped_at', { mode: 'timestamp' }),\n});\n\n// Active streams - tracks resumable stream sessions for multi-client sync\nexport const activeStreams = sqliteTable('active_streams', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n streamId: text('stream_id').notNull().unique(), // Unique stream identifier\n status: text('status', { enum: ['active', 'finished', 'error'] })\n .notNull()\n .default('active'),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n finishedAt: integer('finished_at', { mode: 'timestamp' }),\n});\n\n// Types for JSON columns\nexport interface SessionConfig {\n toolApprovals?: Record<string, boolean>;\n approvalWebhook?: string;\n skillsDirectory?: string;\n maxContextChars?: number;\n}\n\n// AI SDK ModelMessage types - stored directly for accurate context passing\n// These match the exact format from AI SDK's response.messages\nexport type ModelMessage = \n | SystemModelMessage\n | UserModelMessage\n | AssistantModelMessage\n | ToolModelMessage;\n\nexport interface SystemModelMessage {\n role: 'system';\n content: string;\n}\n\nexport interface UserModelMessage {\n role: 'user';\n content: string;\n}\n\nexport interface AssistantModelMessage {\n role: 'assistant';\n content: string | AssistantContentPart[];\n}\n\nexport interface ToolModelMessage {\n role: 'tool';\n content: ToolResultPart[];\n}\n\nexport interface AssistantContentPart {\n type: 'text' | 'tool-call' | 'reasoning';\n text?: string;\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n}\n\nexport interface ToolResultPart {\n type: 'tool-result';\n toolCallId: string;\n toolName: string;\n output: unknown;\n}\n\n// Type exports for queries\nexport type Session = typeof sessions.$inferSelect;\nexport type NewSession = typeof sessions.$inferInsert;\nexport type Message = typeof messages.$inferSelect;\nexport type NewMessage = typeof messages.$inferInsert;\nexport type ToolExecution = typeof toolExecutions.$inferSelect;\nexport type NewToolExecution = typeof toolExecutions.$inferInsert;\nexport type TodoItem = typeof todoItems.$inferSelect;\nexport type NewTodoItem = typeof todoItems.$inferInsert;\nexport type LoadedSkill = typeof loadedSkills.$inferSelect;\nexport type Terminal = typeof terminals.$inferSelect;\nexport type NewTerminal = typeof terminals.$inferInsert;\nexport type ActiveStream = typeof activeStreams.$inferSelect;\nexport type NewActiveStream = typeof activeStreams.$inferInsert;","import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve, dirname, join } from 'node:path';\nimport { homedir, platform } from 'node:os';\nimport {\n SparkcoderConfig,\n SparkcoderConfigSchema,\n ResolvedConfig,\n} from './types.js';\n\nconst CONFIG_FILE_NAMES = [\n 'sparkecoder.config.json',\n 'sparkecoder.json',\n '.sparkecoder.json',\n];\n\n/**\n * Get the standard application data directory for the current OS\n * - macOS: ~/Library/Application Support/sparkecoder\n * - Windows: %APPDATA%/sparkecoder\n * - Linux: ~/.local/share/sparkecoder\n */\nexport function getAppDataDirectory(): string {\n const appName = 'sparkecoder';\n \n switch (platform()) {\n case 'darwin':\n return join(homedir(), 'Library', 'Application Support', appName);\n case 'win32':\n return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), appName);\n default:\n // Linux and other Unix-like systems\n return join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), appName);\n }\n}\n\n/**\n * Ensure the app data directory exists\n */\nexport function ensureAppDataDirectory(): string {\n const dir = getAppDataDirectory();\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\nlet cachedConfig: ResolvedConfig | null = null;\n\n/**\n * Find the config file by searching:\n * 1. Up the directory tree from startDir (project-specific config)\n * 2. In the app data directory (global config)\n */\nfunction findConfigFile(startDir: string): string | null {\n // First, search up the directory tree\n let currentDir = startDir;\n\n while (currentDir !== dirname(currentDir)) {\n for (const fileName of CONFIG_FILE_NAMES) {\n const configPath = resolve(currentDir, fileName);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n currentDir = dirname(currentDir);\n }\n\n // If not found, check the app data directory for a global config\n const appDataDir = getAppDataDirectory();\n for (const fileName of CONFIG_FILE_NAMES) {\n const configPath = join(appDataDir, fileName);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n\n return null;\n}\n\n/**\n * Load and parse the config file\n */\nexport function loadConfig(\n configPath?: string,\n workingDirectory?: string\n): ResolvedConfig {\n const cwd = workingDirectory || process.cwd();\n\n // Try to find config file\n let rawConfig: Partial<SparkcoderConfig> = {};\n let configDir = cwd;\n\n if (configPath) {\n if (!existsSync(configPath)) {\n throw new Error(`Config file not found: ${configPath}`);\n }\n const content = readFileSync(configPath, 'utf-8');\n rawConfig = JSON.parse(content);\n configDir = dirname(resolve(configPath));\n } else {\n const foundPath = findConfigFile(cwd);\n if (foundPath) {\n const content = readFileSync(foundPath, 'utf-8');\n rawConfig = JSON.parse(content);\n configDir = dirname(foundPath);\n }\n }\n\n // Override with environment variables\n if (process.env.SPARKECODER_MODEL) {\n rawConfig.defaultModel = process.env.SPARKECODER_MODEL;\n }\n if (process.env.SPARKECODER_PORT) {\n rawConfig.server = {\n port: parseInt(process.env.SPARKECODER_PORT, 10),\n host: rawConfig.server?.host ?? '127.0.0.1',\n };\n }\n if (process.env.DATABASE_PATH) {\n rawConfig.databasePath = process.env.DATABASE_PATH;\n }\n\n // Parse and validate\n const config = SparkcoderConfigSchema.parse(rawConfig);\n\n // Resolve working directory\n // Priority: CLI argument > absolute path in config > current working directory\n // Note: workingDirectory in config is only used if it's an absolute path,\n // otherwise we default to where the CLI was run from\n let resolvedWorkingDirectory: string;\n if (workingDirectory) {\n // Explicitly passed via CLI\n resolvedWorkingDirectory = workingDirectory;\n } else if (config.workingDirectory && config.workingDirectory !== '.' && config.workingDirectory.startsWith('/')) {\n // Absolute path in config\n resolvedWorkingDirectory = config.workingDirectory;\n } else {\n // Default to current working directory (where CLI was run)\n resolvedWorkingDirectory = process.cwd();\n }\n\n const resolvedSkillsDirectories = [\n resolve(configDir, config.skills?.directory || './skills'),\n // Built-in skills\n resolve(dirname(import.meta.url.replace('file://', '')), '../skills/default'),\n ...(config.skills?.additionalDirectories || []).map((dir) =>\n resolve(configDir, dir)\n ),\n ].filter((dir) => {\n try {\n return existsSync(dir);\n } catch {\n return false;\n }\n });\n\n // Use app data directory for database by default, unless explicitly configured\n let resolvedDatabasePath: string;\n if (config.databasePath && config.databasePath !== './sparkecoder.db') {\n // User explicitly set a custom path\n resolvedDatabasePath = resolve(configDir, config.databasePath);\n } else {\n // Use standard OS app data directory\n const appDataDir = ensureAppDataDirectory();\n resolvedDatabasePath = join(appDataDir, 'sparkecoder.db');\n }\n\n const resolved: ResolvedConfig = {\n ...config,\n server: {\n port: config.server.port,\n host: config.server.host ?? '127.0.0.1',\n },\n resolvedWorkingDirectory,\n resolvedSkillsDirectories,\n resolvedDatabasePath,\n };\n\n cachedConfig = resolved;\n return resolved;\n}\n\n/**\n * Get the cached config (must call loadConfig first)\n */\nexport function getConfig(): ResolvedConfig {\n if (!cachedConfig) {\n throw new Error('Config not loaded. Call loadConfig first.');\n }\n return cachedConfig;\n}\n\n/**\n * Check if a tool requires approval\n */\nexport function requiresApproval(\n toolName: string,\n sessionConfig?: { toolApprovals?: Record<string, boolean> }\n): boolean {\n const config = getConfig();\n\n // Session-level override takes precedence\n if (sessionConfig?.toolApprovals?.[toolName] !== undefined) {\n return sessionConfig.toolApprovals[toolName];\n }\n\n // Check global config\n const globalApprovals = config.toolApprovals as Record<string, boolean>;\n if (globalApprovals[toolName] !== undefined) {\n return globalApprovals[toolName];\n }\n\n // Default: bash requires approval, others don't\n if (toolName === 'bash') {\n return true;\n }\n\n return false;\n}\n\n/**\n * Create a default config file\n */\nexport function createDefaultConfig(): SparkcoderConfig {\n return {\n defaultModel: 'anthropic/claude-opus-4-5',\n // workingDirectory is intentionally not set - defaults to where CLI is run\n toolApprovals: {\n bash: true,\n write_file: false,\n read_file: false,\n load_skill: false,\n todo: false,\n },\n skills: {\n directory: './skills',\n additionalDirectories: [],\n },\n context: {\n maxChars: 200_000,\n autoSummarize: true,\n keepRecentMessages: 10,\n },\n server: {\n port: 3141,\n host: '127.0.0.1',\n },\n databasePath: './sparkecoder.db',\n };\n}\n\n// ============================================\n// API Key Management\n// ============================================\n\nconst API_KEYS_FILE = 'api-keys.json';\n\n// Provider to environment variable mapping\nconst PROVIDER_ENV_MAP: Record<string, string> = {\n anthropic: 'ANTHROPIC_API_KEY',\n openai: 'OPENAI_API_KEY',\n google: 'GOOGLE_GENERATIVE_AI_API_KEY',\n xai: 'XAI_API_KEY',\n 'ai-gateway': 'AI_GATEWAY_API_KEY',\n};\n\n// All supported providers\nexport const SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);\n\ninterface StoredApiKeys {\n [provider: string]: string;\n}\n\n/**\n * Get the path to the API keys file\n */\nfunction getApiKeysPath(): string {\n const appDir = ensureAppDataDirectory();\n return join(appDir, API_KEYS_FILE);\n}\n\n/**\n * Load stored API keys from file\n */\nfunction loadStoredApiKeys(): StoredApiKeys {\n const keysPath = getApiKeysPath();\n if (!existsSync(keysPath)) {\n return {};\n }\n try {\n const content = readFileSync(keysPath, 'utf-8');\n return JSON.parse(content);\n } catch {\n return {};\n }\n}\n\n/**\n * Save API keys to file\n */\nfunction saveStoredApiKeys(keys: StoredApiKeys): void {\n const keysPath = getApiKeysPath();\n writeFileSync(keysPath, JSON.stringify(keys, null, 2), { mode: 0o600 }); // Secure permissions\n}\n\n/**\n * Load API keys from storage into environment variables\n * Called on startup\n */\nexport function loadApiKeysIntoEnv(): void {\n const storedKeys = loadStoredApiKeys();\n \n for (const [provider, envVar] of Object.entries(PROVIDER_ENV_MAP)) {\n // Only set if not already in env (env takes precedence)\n if (!process.env[envVar] && storedKeys[provider]) {\n process.env[envVar] = storedKeys[provider];\n }\n }\n}\n\n/**\n * Set an API key for a provider\n * Saves to storage and sets in current process env\n */\nexport function setApiKey(provider: string, apiKey: string): void {\n const normalizedProvider = provider.toLowerCase();\n const envVar = PROVIDER_ENV_MAP[normalizedProvider];\n \n if (!envVar) {\n throw new Error(`Unknown provider: ${provider}. Supported: ${SUPPORTED_PROVIDERS.join(', ')}`);\n }\n \n // Save to storage\n const storedKeys = loadStoredApiKeys();\n storedKeys[normalizedProvider] = apiKey;\n saveStoredApiKeys(storedKeys);\n \n // Set in current process\n process.env[envVar] = apiKey;\n}\n\n/**\n * Remove an API key for a provider\n */\nexport function removeApiKey(provider: string): void {\n const normalizedProvider = provider.toLowerCase();\n const envVar = PROVIDER_ENV_MAP[normalizedProvider];\n \n if (!envVar) {\n throw new Error(`Unknown provider: ${provider}. Supported: ${SUPPORTED_PROVIDERS.join(', ')}`);\n }\n \n // Remove from storage\n const storedKeys = loadStoredApiKeys();\n delete storedKeys[normalizedProvider];\n saveStoredApiKeys(storedKeys);\n \n // Remove from current process (if it was from storage)\n // Note: We can't know if it was from env or storage, so we don't remove from env\n}\n\n/**\n * Get API key status for all providers\n * Returns masked keys (first 4 and last 4 chars) and source (env/storage/none)\n */\nexport function getApiKeyStatus(): Array<{\n provider: string;\n envVar: string;\n configured: boolean;\n source: 'env' | 'storage' | 'none';\n maskedKey: string | null;\n}> {\n const storedKeys = loadStoredApiKeys();\n \n return SUPPORTED_PROVIDERS.map((provider) => {\n const envVar = PROVIDER_ENV_MAP[provider];\n const envValue = process.env[envVar];\n const storedValue = storedKeys[provider];\n \n let source: 'env' | 'storage' | 'none' = 'none';\n let value: string | undefined;\n \n if (envValue) {\n // Check if it came from storage (by comparing)\n if (storedValue && envValue === storedValue) {\n source = 'storage';\n } else {\n source = 'env';\n }\n value = envValue;\n } else if (storedValue) {\n source = 'storage';\n value = storedValue;\n }\n \n return {\n provider,\n envVar,\n configured: !!value,\n source,\n maskedKey: value ? maskApiKey(value) : null,\n };\n });\n}\n\n/**\n * Mask an API key for display (show first 4 and last 4 chars)\n */\nfunction maskApiKey(key: string): string {\n if (key.length <= 12) {\n return '****' + key.slice(-4);\n }\n return key.slice(0, 4) + '...' + key.slice(-4);\n}\n\nexport * from './types.js';\n","import { z } from 'zod';\n\n// Tool approval configuration\nexport const ToolApprovalConfigSchema = z.object({\n bash: z.boolean().optional().default(true),\n write_file: z.boolean().optional().default(false),\n read_file: z.boolean().optional().default(false),\n load_skill: z.boolean().optional().default(false),\n todo: z.boolean().optional().default(false),\n});\n\n// Skill definition (from frontmatter)\nexport const SkillMetadataSchema = z.object({\n name: z.string(),\n description: z.string(),\n});\n\n// Session-specific config (stored in DB)\nexport const SessionConfigSchema = z.object({\n toolApprovals: z.record(z.string(), z.boolean()).optional(),\n approvalWebhook: z.string().url().optional(),\n skillsDirectory: z.string().optional(),\n maxContextChars: z.number().optional().default(200_000),\n});\n\n// Main sparkecoder config file schema\nexport const SparkcoderConfigSchema = z.object({\n // Default model to use (Vercel AI Gateway format)\n defaultModel: z.string().default('anthropic/claude-opus-4-5'),\n\n // Working directory for file operations\n workingDirectory: z.string().optional(),\n\n // Tool approval settings\n toolApprovals: ToolApprovalConfigSchema.optional().default({}),\n\n // Approval webhook URL (called when approval is needed)\n approvalWebhook: z.string().url().optional(),\n\n // Skills configuration\n skills: z\n .object({\n // Directory containing skill files\n directory: z.string().optional().default('./skills'),\n // Additional skill directories to include\n additionalDirectories: z.array(z.string()).optional().default([]),\n })\n .optional()\n .default({}),\n\n // Context management\n context: z\n .object({\n // Maximum context size before summarization (in characters)\n maxChars: z.number().optional().default(200_000),\n // Enable automatic summarization\n autoSummarize: z.boolean().optional().default(true),\n // Number of recent messages to keep after summarization\n keepRecentMessages: z.number().optional().default(10),\n })\n .optional()\n .default({}),\n\n // Server configuration\n server: z\n .object({\n port: z.number().default(3141),\n host: z.string().default('127.0.0.1'),\n })\n .default({ port: 3141, host: '127.0.0.1' }),\n\n // Database path\n databasePath: z.string().optional().default('./sparkecoder.db'),\n});\n\nexport type ToolApprovalConfig = z.infer<typeof ToolApprovalConfigSchema>;\nexport type SkillMetadata = z.infer<typeof SkillMetadataSchema>;\nexport type SessionConfig = z.infer<typeof SessionConfigSchema>;\nexport type SparkcoderConfig = z.infer<typeof SparkcoderConfigSchema>;\n\n// Runtime config with resolved paths\nexport interface ResolvedConfig extends Omit<SparkcoderConfig, 'server'> {\n server: {\n port: number;\n host: string;\n };\n resolvedWorkingDirectory: string;\n resolvedSkillsDirectories: string[];\n resolvedDatabasePath: string;\n}\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { truncateOutput } from '../utils/truncate.js';\nimport * as tmux from '../terminal/tmux.js';\n\nconst execAsync = promisify(exec);\n\nconst COMMAND_TIMEOUT = 120_000; // 2 minutes for sync commands\nconst MAX_OUTPUT_CHARS = 10_000;\n\n// Commands that are blocked for safety\nconst BLOCKED_COMMANDS = [\n 'rm -rf /',\n 'rm -rf ~',\n 'mkfs',\n 'dd if=/dev/zero',\n ':(){:|:&};:',\n 'chmod -R 777 /',\n];\n\n/**\n * Check if a command is blocked\n */\nfunction isBlockedCommand(command: string): boolean {\n const normalizedCommand = command.toLowerCase().trim();\n return BLOCKED_COMMANDS.some((blocked) =>\n normalizedCommand.includes(blocked.toLowerCase())\n );\n}\n\nexport interface BashToolProgress {\n terminalId: string;\n status: 'started' | 'running' | 'completed';\n command?: string;\n}\n\nexport interface BashToolOptions {\n workingDirectory: string;\n sessionId: string;\n onOutput?: (output: string) => void;\n onProgress?: (progress: BashToolProgress) => void;\n}\n\n// Unified bash tool schema - Option A (minimal flags)\nconst bashInputSchema = z.object({\n command: z\n .string()\n .optional()\n .describe('The command to execute. Required for running new commands.'),\n background: z\n .boolean()\n .default(false)\n .describe('Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID.'),\n id: z\n .string()\n .optional()\n .describe('Terminal ID. Use to get logs from, send input to, or kill an existing terminal.'),\n kill: z\n .boolean()\n .optional()\n .describe('Kill the terminal with the given ID.'),\n tail: z\n .number()\n .optional()\n .describe('Number of lines to return from the end of output (for logs).'),\n input: z\n .string()\n .optional()\n .describe('Send text input to an interactive terminal (requires id). Used for responding to prompts.'),\n key: z\n .enum(['Enter', 'Escape', 'Up', 'Down', 'Left', 'Right', 'Tab', 'C-c', 'C-d', 'y', 'n'])\n .optional()\n .describe('Send a special key to an interactive terminal (requires id). Use \"y\" or \"n\" for yes/no prompts.'),\n});\n\ntype BashInput = z.infer<typeof bashInputSchema>;\n\n// Cache tmux availability at startup\nlet useTmux: boolean | null = null;\n\nasync function shouldUseTmux(): Promise<boolean> {\n if (useTmux === null) {\n useTmux = await tmux.isTmuxAvailable();\n if (!useTmux) {\n console.warn('[bash] tmux not available, using fallback exec mode');\n }\n }\n return useTmux;\n}\n\n/**\n * Fallback implementation using exec (when tmux is not available)\n */\nasync function execFallback(\n command: string,\n workingDirectory: string,\n onOutput?: (output: string) => void\n): Promise<{ success: boolean; output: string; exitCode: number; error?: string }> {\n try {\n const { stdout, stderr } = await execAsync(command, {\n cwd: workingDirectory,\n timeout: COMMAND_TIMEOUT,\n maxBuffer: 10 * 1024 * 1024,\n });\n\n const output = truncateOutput(stdout + (stderr ? `\\n${stderr}` : ''), MAX_OUTPUT_CHARS);\n onOutput?.(output);\n\n return {\n success: true,\n output,\n exitCode: 0,\n };\n } catch (error: any) {\n const output = truncateOutput(\n (error.stdout || '') + (error.stderr ? `\\n${error.stderr}` : ''),\n MAX_OUTPUT_CHARS\n );\n onOutput?.(output || error.message);\n\n if (error.killed) {\n return {\n success: false,\n error: `Command timed out after ${COMMAND_TIMEOUT / 1000} seconds`,\n output,\n exitCode: 124,\n };\n }\n\n return {\n success: false,\n error: error.message,\n output,\n exitCode: error.code ?? 1,\n };\n }\n}\n\nexport function createBashTool(options: BashToolOptions) {\n return tool({\n description: `Execute commands in the terminal. Every command runs in its own session with logs saved to disk.\n\n**Run a command (default - waits for completion):**\nbash({ command: \"npm install\" })\nbash({ command: \"git status\" })\n\n**Run in background (for dev servers, watchers, or interactive commands):**\nbash({ command: \"npm run dev\", background: true })\n→ Returns { id: \"abc123\" } - save this ID\n\n**Check on a background process:**\nbash({ id: \"abc123\" })\nbash({ id: \"abc123\", tail: 50 }) // last 50 lines only\n\n**Stop a background process:**\nbash({ id: \"abc123\", kill: true })\n\n**Respond to interactive prompts (for yes/no questions, etc.):**\nbash({ id: \"abc123\", key: \"y\" }) // send 'y' for yes\nbash({ id: \"abc123\", key: \"n\" }) // send 'n' for no\nbash({ id: \"abc123\", key: \"Enter\" }) // press Enter\nbash({ id: \"abc123\", input: \"my text\" }) // send text input\n\n**IMPORTANT for interactive commands:**\n- Use --yes, -y, or similar flags to avoid prompts when available\n- For create-next-app: add --yes to accept defaults\n- For npm: add --yes or -y to skip confirmation\n- If prompts are unavoidable, run in background mode and use input/key to respond\n\nLogs are saved to .sparkecoder/terminals/{id}/output.log`,\n\n inputSchema: bashInputSchema,\n\n execute: async (inputArgs: BashInput) => {\n const { command, background, id, kill, tail, input: textInput, key } = inputArgs;\n\n // Handle terminal management (id-based operations)\n if (id) {\n // Kill a terminal\n if (kill) {\n const success = await tmux.killTerminal(id);\n return {\n success,\n id,\n status: success ? 'stopped' : 'not_found',\n message: success ? `Terminal ${id} stopped` : `Terminal ${id} not found or already stopped`,\n };\n }\n\n // Send input to an interactive terminal\n if (textInput !== undefined) {\n const success = await tmux.sendInput(id, textInput, { pressEnter: true });\n if (!success) {\n return {\n success: false,\n id,\n error: `Terminal ${id} not found or not running`,\n };\n }\n \n // Wait a moment for the input to be processed, then get logs\n await new Promise(r => setTimeout(r, 300));\n const { output, status } = await tmux.getLogs(id, options.workingDirectory, { tail: tail || 50, sessionId: options.sessionId });\n const truncatedOutput = truncateOutput(output, MAX_OUTPUT_CHARS);\n \n return {\n success: true,\n id,\n output: truncatedOutput,\n status,\n message: `Sent input \"${textInput}\" to terminal`,\n };\n }\n\n // Send a special key to an interactive terminal\n if (key) {\n const success = await tmux.sendKey(id, key);\n if (!success) {\n return {\n success: false,\n id,\n error: `Terminal ${id} not found or not running`,\n };\n }\n \n // Wait a moment for the key to be processed, then get logs\n await new Promise(r => setTimeout(r, 300));\n const { output, status } = await tmux.getLogs(id, options.workingDirectory, { tail: tail || 50, sessionId: options.sessionId });\n const truncatedOutput = truncateOutput(output, MAX_OUTPUT_CHARS);\n \n return {\n success: true,\n id,\n output: truncatedOutput,\n status,\n message: `Sent key \"${key}\" to terminal`,\n };\n }\n\n // Get logs/status from a terminal\n const { output, status } = await tmux.getLogs(id, options.workingDirectory, { tail, sessionId: options.sessionId });\n const truncatedOutput = truncateOutput(output, MAX_OUTPUT_CHARS);\n\n return {\n success: true,\n id,\n output: truncatedOutput,\n status,\n };\n }\n\n // Running a new command requires the command parameter\n if (!command) {\n return {\n success: false,\n error: 'Either \"command\" (to run a new command) or \"id\" (to check/kill/send input) is required',\n };\n }\n\n // Safety check\n if (isBlockedCommand(command)) {\n return {\n success: false,\n error: 'This command is blocked for safety reasons.',\n output: '',\n exitCode: 1,\n };\n }\n\n // Check if we can use tmux\n const canUseTmux = await shouldUseTmux();\n\n if (background) {\n // Background mode\n if (!canUseTmux) {\n return {\n success: false,\n error: 'Background mode requires tmux to be installed. Install with: brew install tmux (macOS) or apt install tmux (Linux)',\n };\n }\n\n // Generate terminal ID upfront and emit progress\n const terminalId = tmux.generateTerminalId();\n options.onProgress?.({ terminalId, status: 'started', command });\n\n const result = await tmux.runBackground(command, options.workingDirectory, {\n sessionId: options.sessionId,\n terminalId,\n });\n\n return {\n success: true,\n id: result.id,\n status: 'running',\n message: `Started background process. Use bash({ id: \"${result.id}\" }) to check logs.`,\n };\n }\n\n // Sync mode (default)\n if (canUseTmux) {\n // Use tmux for sync mode too (unified infrastructure)\n // Generate terminal ID upfront and emit progress so UI can stream output\n const terminalId = tmux.generateTerminalId();\n options.onProgress?.({ terminalId, status: 'started', command });\n\n try {\n const result = await tmux.runSync(command, options.workingDirectory, {\n sessionId: options.sessionId,\n timeout: COMMAND_TIMEOUT,\n terminalId,\n });\n\n const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS);\n options.onOutput?.(truncatedOutput);\n\n // Emit completed status\n options.onProgress?.({ terminalId, status: 'completed', command });\n\n return {\n success: result.exitCode === 0,\n id: result.id,\n output: truncatedOutput,\n exitCode: result.exitCode,\n status: result.status,\n };\n } catch (error: any) {\n options.onProgress?.({ terminalId, status: 'completed', command });\n return {\n success: false,\n error: error.message,\n output: '',\n exitCode: 1,\n };\n }\n } else {\n // Fallback to exec (no tmux)\n const result = await execFallback(command, options.workingDirectory, options.onOutput);\n return {\n success: result.success,\n output: result.output,\n exitCode: result.exitCode,\n error: result.error,\n };\n }\n },\n });\n}\n\nexport type BashTool = ReturnType<typeof createBashTool>;\n","const MAX_OUTPUT_CHARS = 10_000;\n\n/**\n * Truncate a string if it exceeds the max length\n */\nexport function truncateOutput(\n output: string,\n maxChars: number = MAX_OUTPUT_CHARS\n): string {\n if (output.length <= maxChars) {\n return output;\n }\n\n const halfMax = Math.floor(maxChars / 2);\n const truncatedChars = output.length - maxChars;\n\n return (\n output.slice(0, halfMax) +\n `\\n\\n... [TRUNCATED: ${truncatedChars.toLocaleString()} characters omitted] ...\\n\\n` +\n output.slice(-halfMax)\n );\n}\n\n/**\n * Calculate the total character count of messages\n */\nexport function calculateContextSize(messages: Array<{ content: unknown }>): number {\n return messages.reduce((total, msg) => {\n const content = typeof msg.content === 'string' \n ? msg.content \n : JSON.stringify(msg.content);\n return total + content.length;\n }, 0);\n}\n\n/**\n * Format bytes to human readable string\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * Format number with commas\n */\nexport function formatNumber(num: number): string {\n return num.toLocaleString();\n}\n","/**\n * tmux wrapper for terminal session management\n * \n * Provides a thin abstraction over tmux commands for:\n * - Session creation and management\n * - Output capture and logging\n * - Process lifecycle management\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { mkdir, writeFile, readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { nanoid } from 'nanoid';\n\nconst execAsync = promisify(exec);\n\n// Session prefix for all sparkecoder terminals\nconst SESSION_PREFIX = 'spark_';\n\n// Log directory base path - now session-scoped\nconst LOG_BASE_DIR = '.sparkecoder/sessions';\n\nexport interface TerminalMeta {\n id: string;\n command: string;\n cwd: string;\n createdAt: string;\n sessionId: string;\n background: boolean;\n}\n\nexport interface TerminalResult {\n id: string;\n output: string;\n exitCode: number;\n status: 'completed' | 'running' | 'stopped' | 'error';\n}\n\n// Cache tmux availability check\nlet tmuxAvailableCache: boolean | null = null;\n\n/**\n * Check if tmux is installed and available\n */\nexport async function isTmuxAvailable(): Promise<boolean> {\n if (tmuxAvailableCache !== null) {\n return tmuxAvailableCache;\n }\n \n try {\n const { stdout } = await execAsync('tmux -V');\n tmuxAvailableCache = true;\n console.log(`[tmux] Available: ${stdout.trim()}`);\n return true;\n } catch (error) {\n tmuxAvailableCache = false;\n console.log(`[tmux] Not available: ${error instanceof Error ? error.message : 'unknown error'}`);\n return false;\n }\n}\n\n/**\n * Generate a unique terminal ID\n * Ensure it starts with a letter (tmux session names work better this way)\n */\nexport function generateTerminalId(): string {\n // Prefix with 't' to ensure it starts with a letter (nanoid can start with - or _)\n return 't' + nanoid(9);\n}\n\n/**\n * Get the tmux session name for a terminal ID\n */\nexport function getSessionName(terminalId: string): string {\n return `${SESSION_PREFIX}${terminalId}`;\n}\n\n/**\n * Get the log directory for a terminal (session-scoped)\n */\nexport function getLogDir(terminalId: string, workingDirectory: string, sessionId?: string): string {\n if (sessionId) {\n // New session-scoped path: .sparkecoder/sessions/{sessionId}/terminals/{terminalId}/\n return join(workingDirectory, LOG_BASE_DIR, sessionId, 'terminals', terminalId);\n }\n // Fallback for legacy terminals without sessionId\n return join(workingDirectory, '.sparkecoder/terminals', terminalId);\n}\n\n/**\n * Escape a string for shell command\n */\nfunction shellEscape(str: string): string {\n // Use single quotes and escape any single quotes in the string\n return `'${str.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/**\n * Create log directory and metadata file\n */\nasync function initLogDir(terminalId: string, meta: TerminalMeta, workingDirectory: string): Promise<string> {\n const logDir = getLogDir(terminalId, workingDirectory, meta.sessionId);\n await mkdir(logDir, { recursive: true });\n await writeFile(join(logDir, 'meta.json'), JSON.stringify(meta, null, 2));\n // Create empty output.log\n await writeFile(join(logDir, 'output.log'), '');\n return logDir;\n}\n\n/**\n * Poll until a condition is met or timeout\n */\nasync function pollUntil(\n condition: () => Promise<boolean>,\n options: { timeout: number; interval?: number }\n): Promise<boolean> {\n const { timeout, interval = 100 } = options;\n const startTime = Date.now();\n \n while (Date.now() - startTime < timeout) {\n if (await condition()) {\n return true;\n }\n await new Promise(r => setTimeout(r, interval));\n }\n \n return false;\n}\n\n/**\n * Run a command synchronously in tmux (waits for completion)\n */\nexport async function runSync(\n command: string,\n workingDirectory: string,\n options: { sessionId: string; timeout?: number; terminalId?: string }\n): Promise<TerminalResult> {\n if (!options) {\n throw new Error('runSync: options parameter is required (must include sessionId)');\n }\n const id = options.terminalId || generateTerminalId();\n const session = getSessionName(id);\n const logDir = await initLogDir(id, {\n id,\n command,\n cwd: workingDirectory,\n createdAt: new Date().toISOString(),\n sessionId: options.sessionId,\n background: false,\n }, workingDirectory);\n \n const logFile = join(logDir, 'output.log');\n const exitCodeFile = join(logDir, 'exit_code');\n const timeout = options.timeout || 120000; // 2 minute default\n \n try {\n // Wrap command to write exit code to a file when done\n // Also write output to the log file directly (more reliable than pipe-pane for quick commands)\n const wrappedCommand = `(${command}) 2>&1 | tee -a ${shellEscape(logFile)}; echo $? > ${shellEscape(exitCodeFile)}`;\n \n // Start tmux session\n await execAsync(\n `tmux new-session -d -s ${session} -c ${shellEscape(workingDirectory)} ${shellEscape(wrappedCommand)}`,\n { timeout: 5000 }\n );\n \n // Try to pipe output to log file (may fail if command completes quickly, that's ok)\n try {\n await execAsync(\n `tmux pipe-pane -t ${session} -o 'cat >> ${shellEscape(logFile)}'`,\n { timeout: 1000 }\n );\n } catch {\n // Session may have already ended - that's fine, we use tee in the command\n }\n \n // Poll until session ends or timeout\n const completed = await pollUntil(\n async () => {\n try {\n await execAsync(`tmux has-session -t ${session}`, { timeout: 1000 });\n return false; // Session still exists\n } catch {\n return true; // Session ended\n }\n },\n { timeout, interval: 100 }\n );\n \n if (!completed) {\n // Timeout - kill the session\n try {\n await execAsync(`tmux kill-session -t ${session}`, { timeout: 5000 });\n } catch {\n // Ignore\n }\n \n // Read whatever output we have\n let output = '';\n try {\n output = await readFile(logFile, 'utf-8');\n } catch {\n // Ignore\n }\n \n return {\n id,\n output: output.trim(),\n exitCode: 124, // Standard timeout exit code\n status: 'error',\n };\n }\n \n // Session ended - read output and exit code\n // Give a moment for log file to be flushed\n await new Promise(r => setTimeout(r, 50));\n \n let output = '';\n try {\n output = await readFile(logFile, 'utf-8');\n } catch {\n // Ignore\n }\n \n // Read exit code\n let exitCode = 0;\n try {\n if (existsSync(exitCodeFile)) {\n const exitCodeStr = await readFile(exitCodeFile, 'utf-8');\n exitCode = parseInt(exitCodeStr.trim(), 10) || 0;\n }\n } catch {\n // Ignore exit code read errors\n }\n \n return {\n id,\n output: output.trim(),\n exitCode,\n status: 'completed',\n };\n } catch (error: any) {\n // Try to kill the session on any error\n try {\n await execAsync(`tmux kill-session -t ${session}`, { timeout: 5000 });\n } catch {\n // Ignore\n }\n \n throw error;\n }\n}\n\n/**\n * Run a command in the background (returns immediately)\n */\nexport async function runBackground(\n command: string,\n workingDirectory: string,\n options: { sessionId: string; terminalId?: string }\n): Promise<TerminalResult> {\n if (!options) {\n throw new Error('runBackground: options parameter is required (must include sessionId)');\n }\n const id = options.terminalId || generateTerminalId();\n const session = getSessionName(id);\n const logDir = await initLogDir(id, {\n id,\n command,\n cwd: workingDirectory,\n createdAt: new Date().toISOString(),\n sessionId: options.sessionId,\n background: true,\n }, workingDirectory);\n \n const logFile = join(logDir, 'output.log');\n \n // Wrap command to log output via tee (more reliable than pipe-pane)\n const wrappedCommand = `(${command}) 2>&1 | tee -a ${shellEscape(logFile)}`;\n \n // Start tmux session (don't wait for completion)\n await execAsync(\n `tmux new-session -d -s ${session} -c ${shellEscape(workingDirectory)} ${shellEscape(wrappedCommand)}`,\n { timeout: 5000 }\n );\n \n return {\n id,\n output: '',\n exitCode: 0,\n status: 'running',\n };\n}\n\n/**\n * Get logs from a terminal\n */\nexport async function getLogs(\n terminalId: string,\n workingDirectory: string,\n options: { tail?: number; sessionId?: string } = {}\n): Promise<{ output: string; status: 'running' | 'stopped' | 'unknown' }> {\n const session = getSessionName(terminalId);\n const logDir = getLogDir(terminalId, workingDirectory, options.sessionId);\n const logFile = join(logDir, 'output.log');\n \n // Check if session is still running\n let isRunning = false;\n try {\n await execAsync(`tmux has-session -t ${session}`, { timeout: 5000 });\n isRunning = true;\n } catch {\n // Session not running\n }\n \n // Try to capture from tmux first (more up-to-date)\n if (isRunning) {\n try {\n const lines = options.tail || 1000;\n const { stdout } = await execAsync(\n `tmux capture-pane -t ${session} -p -S -${lines}`,\n { timeout: 5000, maxBuffer: 10 * 1024 * 1024 }\n );\n return { output: stdout.trim(), status: 'running' };\n } catch {\n // Fall through to file-based approach\n }\n }\n \n // Fall back to log file\n try {\n let output = await readFile(logFile, 'utf-8');\n \n if (options.tail) {\n const lines = output.split('\\n');\n output = lines.slice(-options.tail).join('\\n');\n }\n \n return { output: output.trim(), status: isRunning ? 'running' : 'stopped' };\n } catch {\n return { output: '', status: 'unknown' };\n }\n}\n\n/**\n * Check if a terminal is running\n */\nexport async function isRunning(terminalId: string): Promise<boolean> {\n const session = getSessionName(terminalId);\n try {\n await execAsync(`tmux has-session -t ${session}`, { timeout: 5000 });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Kill a terminal session\n */\nexport async function killTerminal(terminalId: string): Promise<boolean> {\n const session = getSessionName(terminalId);\n try {\n await execAsync(`tmux kill-session -t ${session}`, { timeout: 5000 });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * List all sparkecoder terminal sessions\n */\nexport async function listSessions(): Promise<string[]> {\n try {\n const { stdout } = await execAsync(\n `tmux list-sessions -F '#{session_name}' 2>/dev/null || true`,\n { timeout: 5000 }\n );\n \n return stdout\n .trim()\n .split('\\n')\n .filter(name => name.startsWith(SESSION_PREFIX))\n .map(name => name.slice(SESSION_PREFIX.length));\n } catch {\n return [];\n }\n}\n\n/**\n * Get metadata for a terminal\n */\nexport async function getMeta(terminalId: string, workingDirectory: string, sessionId?: string): Promise<TerminalMeta | null> {\n const logDir = getLogDir(terminalId, workingDirectory, sessionId);\n const metaFile = join(logDir, 'meta.json');\n \n try {\n const content = await readFile(metaFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * List all terminals for a session\n */\nexport async function listSessionTerminals(\n sessionId: string,\n workingDirectory: string\n): Promise<TerminalMeta[]> {\n const terminalsDir = join(workingDirectory, LOG_BASE_DIR, sessionId, 'terminals');\n const terminals: TerminalMeta[] = [];\n \n try {\n const { readdir } = await import('node:fs/promises');\n const entries = await readdir(terminalsDir, { withFileTypes: true });\n \n for (const entry of entries) {\n if (entry.isDirectory()) {\n const meta = await getMeta(entry.name, workingDirectory, sessionId);\n if (meta) {\n terminals.push(meta);\n }\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n \n return terminals;\n}\n\n/**\n * Send input (keystrokes) to a running terminal\n * Use this to respond to interactive prompts\n */\nexport async function sendInput(terminalId: string, input: string, options: { pressEnter?: boolean } = {}): Promise<boolean> {\n const session = getSessionName(terminalId);\n const { pressEnter = true } = options;\n \n try {\n // Check if session exists first\n await execAsync(`tmux has-session -t ${session}`, { timeout: 1000 });\n \n // Send the input using tmux send-keys with -l (literal) flag\n await execAsync(\n `tmux send-keys -t ${session} -l ${shellEscape(input)}`,\n { timeout: 1000 }\n );\n \n // Send Enter key separately if requested\n if (pressEnter) {\n await execAsync(\n `tmux send-keys -t ${session} Enter`,\n { timeout: 1000 }\n );\n }\n \n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Send special keys to a terminal (like arrow keys, escape, etc.)\n */\nexport async function sendKey(terminalId: string, key: 'Enter' | 'Escape' | 'Up' | 'Down' | 'Left' | 'Right' | 'Tab' | 'C-c' | 'C-d' | 'y' | 'n'): Promise<boolean> {\n const session = getSessionName(terminalId);\n \n try {\n await execAsync(`tmux has-session -t ${session}`, { timeout: 1000 });\n await execAsync(`tmux send-keys -t ${session} ${key}`, { timeout: 1000 });\n return true;\n } catch {\n return false;\n }\n}\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { readFile, stat } from 'node:fs/promises';\nimport { resolve, relative, isAbsolute } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { truncateOutput } from '../utils/truncate.js';\n\nconst MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB\nconst MAX_OUTPUT_CHARS = 50_000;\n\nexport interface ReadFileToolOptions {\n workingDirectory: string;\n}\n\nconst readFileInputSchema = z.object({\n path: z\n .string()\n .describe('The path to the file to read. Can be relative to working directory or absolute.'),\n startLine: z\n .number()\n .optional()\n .describe('Optional: Start reading from this line number (1-indexed)'),\n endLine: z\n .number()\n .optional()\n .describe('Optional: Stop reading at this line number (1-indexed, inclusive)'),\n});\n\nexport function createReadFileTool(options: ReadFileToolOptions) {\n return tool({\n description: `Read the contents of a file. Provide a path relative to the working directory (${options.workingDirectory}) or an absolute path.\nLarge files will be automatically truncated. Binary files are not supported.\nUse this to understand existing code, check file contents, or gather context.`,\n\n inputSchema: readFileInputSchema,\n\n execute: async ({ path, startLine, endLine }: z.infer<typeof readFileInputSchema>) => {\n try {\n // Resolve the path\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(options.workingDirectory, path);\n\n // Security check: ensure path is within working directory or is explicitly absolute\n const relativePath = relative(options.workingDirectory, absolutePath);\n if (relativePath.startsWith('..') && !isAbsolute(path)) {\n return {\n success: false,\n error: 'Path escapes the working directory. Use an absolute path if intentional.',\n content: null,\n };\n }\n\n // Check if file exists\n if (!existsSync(absolutePath)) {\n return {\n success: false,\n error: `File not found: ${path}`,\n content: null,\n };\n }\n\n // Check file size\n const stats = await stat(absolutePath);\n if (stats.size > MAX_FILE_SIZE) {\n return {\n success: false,\n error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,\n content: null,\n };\n }\n\n // Check if it's a directory\n if (stats.isDirectory()) {\n return {\n success: false,\n error: 'Path is a directory, not a file. Use bash with \"ls\" to list directory contents.',\n content: null,\n };\n }\n\n // Read the file\n let content = await readFile(absolutePath, 'utf-8');\n\n // Handle line range\n if (startLine !== undefined || endLine !== undefined) {\n const lines = content.split('\\n');\n const start = (startLine ?? 1) - 1;\n const end = endLine ?? lines.length;\n \n if (start < 0 || start >= lines.length) {\n return {\n success: false,\n error: `Start line ${startLine} is out of range. File has ${lines.length} lines.`,\n content: null,\n };\n }\n\n content = lines.slice(start, end).join('\\n');\n \n // Add line number context\n const lineNumbers = lines\n .slice(start, end)\n .map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`)\n .join('\\n');\n \n content = lineNumbers;\n }\n\n // Truncate if necessary\n const truncatedContent = truncateOutput(content, MAX_OUTPUT_CHARS);\n const wasTruncated = truncatedContent.length < content.length;\n\n return {\n success: true,\n path: absolutePath,\n relativePath: relative(options.workingDirectory, absolutePath),\n content: truncatedContent,\n lineCount: content.split('\\n').length,\n wasTruncated,\n sizeBytes: stats.size,\n };\n } catch (error: any) {\n // Check for binary file\n if (error.code === 'ERR_INVALID_ARG_VALUE' || error.message.includes('encoding')) {\n return {\n success: false,\n error: 'File appears to be binary and cannot be read as text.',\n content: null,\n };\n }\n\n return {\n success: false,\n error: error.message,\n content: null,\n };\n }\n },\n });\n}\n\nexport type ReadFileTool = ReturnType<typeof createReadFileTool>;\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { resolve, relative, isAbsolute, dirname } from 'node:path';\nimport { existsSync } from 'node:fs';\n\nexport interface WriteFileToolOptions {\n workingDirectory: string;\n}\n\nconst writeFileInputSchema = z.object({\n path: z\n .string()\n .describe('The path to the file. Can be relative to working directory or absolute.'),\n mode: z\n .enum(['full', 'str_replace'])\n .describe('Write mode: \"full\" for complete file write, \"str_replace\" for targeted string replacement'),\n content: z\n .string()\n .optional()\n .describe('For \"full\" mode: The complete content to write to the file'),\n old_string: z\n .string()\n .optional()\n .describe('For \"str_replace\" mode: The exact string to find and replace'),\n new_string: z\n .string()\n .optional()\n .describe('For \"str_replace\" mode: The string to replace old_string with'),\n});\n\nexport function createWriteFileTool(options: WriteFileToolOptions) {\n return tool({\n description: `Write content to a file. Supports two modes:\n1. \"full\" - Write the entire file content (creates new file or replaces existing)\n2. \"str_replace\" - Replace a specific string in an existing file (for precise edits)\n\nFor str_replace mode:\n- Provide the exact string to find (old_string) and its replacement (new_string)\n- The old_string must match EXACTLY (including whitespace and indentation)\n- Only the first occurrence is replaced\n- Use this for surgical edits to existing code\n\nFor full mode:\n- Provide the complete file content\n- Creates parent directories if they don't exist\n- Use this for new files or complete rewrites\n\nWorking directory: ${options.workingDirectory}`,\n\n inputSchema: writeFileInputSchema,\n\n execute: async ({ path, mode, content, old_string, new_string }: z.infer<typeof writeFileInputSchema>) => {\n try {\n // Resolve the path\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(options.workingDirectory, path);\n\n // Security check\n const relativePath = relative(options.workingDirectory, absolutePath);\n if (relativePath.startsWith('..') && !isAbsolute(path)) {\n return {\n success: false,\n error: 'Path escapes the working directory. Use an absolute path if intentional.',\n };\n }\n\n if (mode === 'full') {\n // Full file write\n if (content === undefined) {\n return {\n success: false,\n error: 'Content is required for \"full\" mode',\n };\n }\n\n // Create parent directories if needed\n const dir = dirname(absolutePath);\n if (!existsSync(dir)) {\n await mkdir(dir, { recursive: true });\n }\n\n const existed = existsSync(absolutePath);\n await writeFile(absolutePath, content, 'utf-8');\n\n return {\n success: true,\n path: absolutePath,\n relativePath: relative(options.workingDirectory, absolutePath),\n mode: 'full',\n action: existed ? 'replaced' : 'created',\n bytesWritten: Buffer.byteLength(content, 'utf-8'),\n lineCount: content.split('\\n').length,\n };\n } else if (mode === 'str_replace') {\n // String replacement mode\n if (old_string === undefined || new_string === undefined) {\n return {\n success: false,\n error: 'Both old_string and new_string are required for \"str_replace\" mode',\n };\n }\n\n if (!existsSync(absolutePath)) {\n return {\n success: false,\n error: `File not found: ${path}. Use \"full\" mode to create new files.`,\n };\n }\n\n // Read current content\n const currentContent = await readFile(absolutePath, 'utf-8');\n\n // Check if old_string exists\n if (!currentContent.includes(old_string)) {\n // Provide helpful debugging info\n const lines = currentContent.split('\\n');\n const preview = lines.slice(0, 20).join('\\n');\n \n return {\n success: false,\n error: 'old_string not found in file. The string must match EXACTLY including whitespace.',\n hint: 'Check for differences in indentation, line endings, or invisible characters.',\n filePreview: lines.length > 20 \n ? `${preview}\\n... (${lines.length - 20} more lines)`\n : preview,\n };\n }\n\n // Check for multiple occurrences\n const occurrences = currentContent.split(old_string).length - 1;\n if (occurrences > 1) {\n return {\n success: false,\n error: `Found ${occurrences} occurrences of old_string. Please provide more context to make it unique.`,\n hint: 'Include surrounding lines or more specific content in old_string.',\n };\n }\n\n // Perform replacement\n const newContent = currentContent.replace(old_string, new_string);\n await writeFile(absolutePath, newContent, 'utf-8');\n\n // Calculate diff info\n const oldLines = old_string.split('\\n').length;\n const newLines = new_string.split('\\n').length;\n\n return {\n success: true,\n path: absolutePath,\n relativePath: relative(options.workingDirectory, absolutePath),\n mode: 'str_replace',\n linesRemoved: oldLines,\n linesAdded: newLines,\n lineDelta: newLines - oldLines,\n };\n }\n\n return {\n success: false,\n error: `Invalid mode: ${mode}`,\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\nexport type WriteFileTool = ReturnType<typeof createWriteFileTool>;\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { todoQueries, TodoItem } from '../db/index.js';\n\nexport interface TodoToolOptions {\n sessionId: string;\n}\n\nconst todoInputSchema = z.object({\n action: z\n .enum(['add', 'list', 'mark', 'clear'])\n .describe('The action to perform on the todo list'),\n items: z\n .array(\n z.object({\n content: z.string().describe('Description of the task'),\n order: z.number().optional().describe('Optional order/priority (lower = higher priority)'),\n })\n )\n .optional()\n .describe('For \"add\" action: Array of todo items to add'),\n todoId: z\n .string()\n .optional()\n .describe('For \"mark\" action: The ID of the todo item to update'),\n status: z\n .enum(['pending', 'in_progress', 'completed', 'cancelled'])\n .optional()\n .describe('For \"mark\" action: The new status for the todo item'),\n});\n\nexport function createTodoTool(options: TodoToolOptions) {\n return tool({\n description: `Manage your task list for the current session. Use this to:\n- Break down complex tasks into smaller steps\n- Track progress on multi-step operations\n- Organize your work systematically\n\nAvailable actions:\n- \"add\": Add one or more new todo items to the list\n- \"list\": View all current todo items and their status\n- \"mark\": Update the status of a todo item (pending, in_progress, completed, cancelled)\n- \"clear\": Remove all todo items from the list\n\nBest practices:\n- Add todos before starting complex tasks\n- Mark items as \"in_progress\" when actively working on them\n- Update status as you complete each step`,\n\n inputSchema: todoInputSchema,\n\n execute: async ({ action, items, todoId, status }: z.infer<typeof todoInputSchema>) => {\n try {\n switch (action) {\n case 'add': {\n if (!items || items.length === 0) {\n return {\n success: false,\n error: 'No items provided. Include at least one todo item.',\n };\n }\n\n const created = todoQueries.createMany(options.sessionId, items);\n \n return {\n success: true,\n action: 'add',\n itemsAdded: created.length,\n items: created.map(formatTodoItem),\n };\n }\n\n case 'list': {\n const todos = todoQueries.getBySession(options.sessionId);\n \n const stats = {\n total: todos.length,\n pending: todos.filter((t) => t.status === 'pending').length,\n inProgress: todos.filter((t) => t.status === 'in_progress').length,\n completed: todos.filter((t) => t.status === 'completed').length,\n cancelled: todos.filter((t) => t.status === 'cancelled').length,\n };\n\n return {\n success: true,\n action: 'list',\n stats,\n items: todos.map(formatTodoItem),\n };\n }\n\n case 'mark': {\n if (!todoId) {\n return {\n success: false,\n error: 'todoId is required for \"mark\" action',\n };\n }\n\n if (!status) {\n return {\n success: false,\n error: 'status is required for \"mark\" action',\n };\n }\n\n const updated = todoQueries.updateStatus(todoId, status);\n \n if (!updated) {\n return {\n success: false,\n error: `Todo item not found: ${todoId}`,\n };\n }\n\n return {\n success: true,\n action: 'mark',\n item: formatTodoItem(updated),\n };\n }\n\n case 'clear': {\n const count = todoQueries.clearSession(options.sessionId);\n \n return {\n success: true,\n action: 'clear',\n itemsRemoved: count,\n };\n }\n\n default:\n return {\n success: false,\n error: `Unknown action: ${action}`,\n };\n }\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\nfunction formatTodoItem(item: TodoItem) {\n return {\n id: item.id,\n content: item.content,\n status: item.status,\n order: item.order,\n createdAt: item.createdAt.toISOString(),\n };\n}\n\nexport type TodoTool = ReturnType<typeof createTodoTool>;\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { loadAllSkills, loadSkillContent, formatSkillsForContext } from '../skills/index.js';\nimport { skillQueries } from '../db/index.js';\n\nexport interface LoadSkillToolOptions {\n sessionId: string;\n skillsDirectories: string[];\n}\n\nconst loadSkillInputSchema = z.object({\n action: z\n .enum(['list', 'load'])\n .describe('Action to perform: \"list\" to see available skills, \"load\" to load a skill'),\n skillName: z\n .string()\n .optional()\n .describe('For \"load\" action: The name of the skill to load'),\n});\n\nexport function createLoadSkillTool(options: LoadSkillToolOptions) {\n return tool({\n description: `Load a skill document into the conversation context. Skills are specialized knowledge files that provide guidance on specific topics like debugging, code review, architecture patterns, etc.\n\nAvailable actions:\n- \"list\": Show all available skills with their descriptions\n- \"load\": Load a specific skill's full content into context\n\nUse this when you need specialized knowledge or guidance for a particular task.\nOnce loaded, a skill's content will be available in the conversation context.`,\n\n inputSchema: loadSkillInputSchema,\n\n execute: async ({ action, skillName }: z.infer<typeof loadSkillInputSchema>) => {\n try {\n switch (action) {\n case 'list': {\n const skills = await loadAllSkills(options.skillsDirectories);\n \n return {\n success: true,\n action: 'list',\n skillCount: skills.length,\n skills: skills.map((s) => ({\n name: s.name,\n description: s.description,\n })),\n formatted: formatSkillsForContext(skills),\n };\n }\n\n case 'load': {\n if (!skillName) {\n return {\n success: false,\n error: 'skillName is required for \"load\" action',\n };\n }\n\n // Check if already loaded\n if (skillQueries.isLoaded(options.sessionId, skillName)) {\n return {\n success: false,\n error: `Skill \"${skillName}\" is already loaded in this session`,\n };\n }\n\n // Load the skill content\n const skill = await loadSkillContent(skillName, options.skillsDirectories);\n \n if (!skill) {\n const allSkills = await loadAllSkills(options.skillsDirectories);\n return {\n success: false,\n error: `Skill \"${skillName}\" not found`,\n availableSkills: allSkills.map((s) => s.name),\n };\n }\n\n // Record that we loaded this skill\n skillQueries.load(options.sessionId, skillName);\n\n return {\n success: true,\n action: 'load',\n skillName: skill.name,\n description: skill.description,\n content: skill.content,\n contentLength: skill.content.length,\n };\n }\n\n default:\n return {\n success: false,\n error: `Unknown action: ${action}`,\n };\n }\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\nexport type LoadSkillTool = ReturnType<typeof createLoadSkillTool>;\n","import { readFile, readdir } from 'node:fs/promises';\nimport { resolve, basename, extname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { SkillMetadata, SkillMetadataSchema } from '../config/types.js';\n\nexport interface Skill {\n name: string;\n description: string;\n filePath: string;\n content?: string; // Only loaded when explicitly requested\n}\n\nexport interface SkillWithContent extends Skill {\n content: string;\n}\n\n/**\n * Parse skill metadata from frontmatter\n */\nfunction parseSkillFrontmatter(content: string): { metadata: SkillMetadata; body: string } | null {\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n \n if (!frontmatterMatch) {\n return null;\n }\n\n const [, frontmatter, body] = frontmatterMatch;\n \n try {\n // Simple YAML-like parsing for name and description\n const lines = frontmatter.split('\\n');\n const data: Record<string, string> = {};\n \n for (const line of lines) {\n const colonIndex = line.indexOf(':');\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim();\n let value = line.slice(colonIndex + 1).trim();\n // Remove quotes if present\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n data[key] = value;\n }\n }\n\n const metadata = SkillMetadataSchema.parse(data);\n return { metadata, body: body.trim() };\n } catch {\n return null;\n }\n}\n\n/**\n * Get skill name from filename if no frontmatter\n */\nfunction getSkillNameFromPath(filePath: string): string {\n return basename(filePath, extname(filePath))\n .replace(/[-_]/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Load all skills from a directory (metadata only)\n */\nexport async function loadSkillsFromDirectory(directory: string): Promise<Skill[]> {\n if (!existsSync(directory)) {\n return [];\n }\n\n const skills: Skill[] = [];\n const files = await readdir(directory);\n\n for (const file of files) {\n if (!file.endsWith('.md')) continue;\n\n const filePath = resolve(directory, file);\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n\n if (parsed) {\n skills.push({\n name: parsed.metadata.name,\n description: parsed.metadata.description,\n filePath,\n });\n } else {\n // Use filename as name, first paragraph as description\n const name = getSkillNameFromPath(filePath);\n const firstParagraph = content.split('\\n\\n')[0]?.slice(0, 200) || 'No description';\n \n skills.push({\n name,\n description: firstParagraph.replace(/^#\\s*/, '').trim(),\n filePath,\n });\n }\n }\n\n return skills;\n}\n\n/**\n * Load all skills from multiple directories\n */\nexport async function loadAllSkills(directories: string[]): Promise<Skill[]> {\n const allSkills: Skill[] = [];\n const seenNames = new Set<string>();\n\n for (const dir of directories) {\n const skills = await loadSkillsFromDirectory(dir);\n for (const skill of skills) {\n // Avoid duplicates (first one wins)\n if (!seenNames.has(skill.name.toLowerCase())) {\n seenNames.add(skill.name.toLowerCase());\n allSkills.push(skill);\n }\n }\n }\n\n return allSkills;\n}\n\n/**\n * Load a skill's full content by name\n */\nexport async function loadSkillContent(\n skillName: string,\n directories: string[]\n): Promise<SkillWithContent | null> {\n const allSkills = await loadAllSkills(directories);\n const skill = allSkills.find(\n (s) => s.name.toLowerCase() === skillName.toLowerCase()\n );\n\n if (!skill) {\n return null;\n }\n\n const content = await readFile(skill.filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n\n return {\n ...skill,\n content: parsed ? parsed.body : content,\n };\n}\n\n/**\n * Format skills list for context\n */\nexport function formatSkillsForContext(skills: Skill[]): string {\n if (skills.length === 0) {\n return 'No skills available.';\n }\n\n const lines = ['Available skills (use load_skill tool to load into context):'];\n for (const skill of skills) {\n lines.push(`- ${skill.name}: ${skill.description}`);\n }\n\n return lines.join('\\n');\n}\n","import { ToolSet } from 'ai';\nimport { createBashTool, BashToolOptions, BashToolProgress } from './bash.js';\nimport { createReadFileTool, ReadFileToolOptions } from './read-file.js';\nimport { createWriteFileTool, WriteFileToolOptions } from './write-file.js';\nimport { createTodoTool, TodoToolOptions } from './todo.js';\nimport { createLoadSkillTool, LoadSkillToolOptions } from './load-skill.js';\n\nexport interface CreateToolsOptions {\n sessionId: string;\n workingDirectory: string;\n skillsDirectories: string[];\n onBashOutput?: (output: string) => void;\n onBashProgress?: (progress: BashToolProgress) => void;\n}\n\n/**\n * Create all tools for an agent session\n */\nexport function createTools(options: CreateToolsOptions): ToolSet {\n return {\n bash: createBashTool({\n workingDirectory: options.workingDirectory,\n sessionId: options.sessionId,\n onOutput: options.onBashOutput,\n onProgress: options.onBashProgress,\n }),\n\n read_file: createReadFileTool({\n workingDirectory: options.workingDirectory,\n }),\n\n write_file: createWriteFileTool({\n workingDirectory: options.workingDirectory,\n }),\n\n todo: createTodoTool({\n sessionId: options.sessionId,\n }),\n\n load_skill: createLoadSkillTool({\n sessionId: options.sessionId,\n skillsDirectories: options.skillsDirectories,\n }),\n };\n}\n\n// Re-export individual tool creators for customization\nexport { createBashTool } from './bash.js';\nexport { createReadFileTool } from './read-file.js';\nexport { createWriteFileTool } from './write-file.js';\nexport { createTodoTool } from './todo.js';\nexport { createLoadSkillTool } from './load-skill.js';\n\n// Export types\nexport type { BashToolOptions, BashToolProgress } from './bash.js';\nexport type { ReadFileToolOptions } from './read-file.js';\nexport type { WriteFileToolOptions } from './write-file.js';\nexport type { TodoToolOptions } from './todo.js';\nexport type { LoadSkillToolOptions } from './load-skill.js';","import { generateText, type ModelMessage as AIModelMessage } from 'ai';\nimport { gateway } from '@ai-sdk/gateway';\nimport { messageQueries, ModelMessage } from '../db/index.js';\nimport { calculateContextSize } from '../utils/truncate.js';\nimport { createSummaryPrompt } from './prompts.js';\nimport { getConfig } from '../config/index.js';\n\nexport interface ContextManagerOptions {\n sessionId: string;\n maxContextChars: number;\n keepRecentMessages: number;\n autoSummarize: boolean;\n}\n\n/**\n * Manages conversation context including history and summarization\n * \n * Uses AI SDK's ModelMessage format directly for accurate message passing.\n * Messages are stored in the exact format returned by response.messages.\n */\nexport class ContextManager {\n private sessionId: string;\n private maxContextChars: number;\n private keepRecentMessages: number;\n private autoSummarize: boolean;\n private summary: string | null = null;\n\n constructor(options: ContextManagerOptions) {\n this.sessionId = options.sessionId;\n this.maxContextChars = options.maxContextChars;\n this.keepRecentMessages = options.keepRecentMessages;\n this.autoSummarize = options.autoSummarize;\n }\n\n /**\n * Get messages for the current context\n * Returns ModelMessage[] that can be passed directly to streamText/generateText\n */\n async getMessages(): Promise<AIModelMessage[]> {\n let modelMessages = messageQueries.getModelMessages(this.sessionId) as AIModelMessage[];\n\n // Calculate context size\n const contextSize = calculateContextSize(modelMessages);\n\n // Check if we need to summarize\n if (this.autoSummarize && contextSize > this.maxContextChars) {\n modelMessages = await this.summarizeContext(modelMessages);\n }\n\n // Prepend summary if exists\n if (this.summary) {\n modelMessages = [\n {\n role: 'system' as const,\n content: `[Previous conversation summary]\\n${this.summary}`,\n },\n ...modelMessages,\n ];\n }\n\n return modelMessages;\n }\n\n /**\n * Summarize older messages to reduce context size\n */\n private async summarizeContext(messages: AIModelMessage[]): Promise<AIModelMessage[]> {\n if (messages.length <= this.keepRecentMessages) {\n return messages;\n }\n\n // Split into old and recent messages\n const splitIndex = messages.length - this.keepRecentMessages;\n const oldMessages = messages.slice(0, splitIndex);\n const recentMessages = messages.slice(splitIndex);\n\n // Format old messages for summarization\n const historyText = oldMessages\n .map((msg) => {\n const content = typeof msg.content === 'string' \n ? msg.content \n : JSON.stringify(msg.content);\n return `[${msg.role}]: ${content}`;\n })\n .join('\\n\\n');\n\n // Generate summary\n try {\n const config = getConfig();\n const summaryPrompt = createSummaryPrompt(historyText);\n\n const result = await generateText({\n model: gateway(config.defaultModel) as any,\n prompt: summaryPrompt,\n });\n\n this.summary = result.text;\n \n console.log(`[Context] Summarized ${oldMessages.length} messages into ${this.summary.length} chars`);\n\n return recentMessages;\n } catch (error) {\n console.error('[Context] Failed to summarize:', error);\n // Fall back to truncating old messages\n return recentMessages;\n }\n }\n\n /**\n * Add a user message to the context\n */\n addUserMessage(text: string): void {\n const userMessage: ModelMessage = {\n role: 'user',\n content: text,\n };\n messageQueries.create(this.sessionId, userMessage);\n }\n\n /**\n * Add response messages from AI SDK directly\n * This is the preferred method - use result.response.messages from streamText/generateText\n */\n addResponseMessages(messages: AIModelMessage[]): void {\n messageQueries.addMany(this.sessionId, messages as ModelMessage[]);\n }\n\n /**\n * Get current context statistics\n */\n getStats(): { messageCount: number; contextChars: number; hasSummary: boolean } {\n const messages = messageQueries.getModelMessages(this.sessionId) as AIModelMessage[];\n \n return {\n messageCount: messages.length,\n contextChars: calculateContextSize(messages),\n hasSummary: this.summary !== null,\n };\n }\n\n /**\n * Clear all messages in the context\n */\n clear(): void {\n messageQueries.deleteBySession(this.sessionId);\n this.summary = null;\n }\n}\n","import os from 'node:os';\nimport { loadAllSkills, formatSkillsForContext } from '../skills/index.js';\nimport { todoQueries, TodoItem } from '../db/index.js';\n\n/**\n * Get platform-specific search instructions\n */\nfunction getSearchInstructions(): string {\n const platform = process.platform;\n \n const common = `- **Prefer \\`read_file\\` over shell commands** for reading files - don't use \\`cat\\`, \\`head\\`, or \\`tail\\` when \\`read_file\\` is available\n- **Avoid unbounded searches** - always scope searches with glob patterns and directory paths to prevent overwhelming output\n- **Search strategically**: Start with specific patterns and directories, then broaden only if needed`;\n\n if (platform === 'win32') {\n return `${common}\n- **Find files**: \\`dir /s /b *.ts\\` or PowerShell: \\`Get-ChildItem -Recurse -Filter *.ts\\`\n- **Search content**: \\`findstr /s /n \"pattern\" *.ts\\` or PowerShell: \\`Select-String -Pattern \"pattern\" -Path *.ts -Recurse\\`\n- **If ripgrep (\\`rg\\`) is installed**: \\`rg \"pattern\" -t ts src/\\` - faster and respects .gitignore`;\n }\n \n // Unix-like (darwin, linux, etc.)\n return `${common}\n- **Find files**: \\`find . -name \"*.ts\"\\` or \\`find src/ -type f -name \"*.tsx\"\\`\n- **Search content**: \\`grep -rn \"pattern\" --include=\"*.ts\" src/\\` - use \\`-l\\` for filenames only, \\`-c\\` for counts\n- **If ripgrep (\\`rg\\`) is installed**: \\`rg \"pattern\" -t ts src/\\` - faster and respects .gitignore`;\n}\n\n/**\n * Build the system prompt for the coding agent\n */\nexport async function buildSystemPrompt(options: {\n workingDirectory: string;\n skillsDirectories: string[];\n sessionId: string;\n customInstructions?: string;\n}): Promise<string> {\n const { workingDirectory, skillsDirectories, sessionId, customInstructions } = options;\n\n // Load available skills\n const skills = await loadAllSkills(skillsDirectories);\n const skillsContext = formatSkillsForContext(skills);\n\n // Load current todos\n const todos = todoQueries.getBySession(sessionId);\n const todosContext = formatTodosForContext(todos);\n\n // Get environment info\n const platform = process.platform === 'win32' ? 'Windows' : process.platform === 'darwin' ? 'macOS' : 'Linux';\n const currentDate = new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n const searchInstructions = getSearchInstructions();\n\n const systemPrompt = `You are SparkECoder, an expert AI coding assistant. You help developers write, debug, and improve code.\n\n## Environment\n- **Platform**: ${platform} (${os.release()})\n- **Date**: ${currentDate}\n- **Working Directory**: ${workingDirectory}\n\n## Core Capabilities\nYou have access to powerful tools for:\n- **bash**: Execute commands in the terminal (see below for details)\n- **read_file**: Read file contents to understand code and context\n- **write_file**: Create new files or edit existing ones (supports targeted string replacement)\n- **todo**: Manage your task list to track progress on complex operations\n- **load_skill**: Load specialized knowledge documents for specific tasks\n\nUse the TODO tool to manage your task list to track progress on complex operations. Always ask the user what they want to do specifically before doing it, and make a plan. \nStep 1 of the plan should be researching files and understanding the components/structure of what you're working on (if you don't already have context), then after u have done that, plan out the rest of the tasks u need to do. \nYou can clear the todo and restart it, and do multiple things inside of one session.\n\n### bash Tool\nThe bash tool runs commands in the terminal. Every command runs in its own session with logs saved to disk.\n\n**Run a command (default - waits for completion):**\n\\`\\`\\`\nbash({ command: \"npm install\" })\nbash({ command: \"git status\" })\n\\`\\`\\`\n\n**Run in background (for dev servers, watchers):**\n\\`\\`\\`\nbash({ command: \"npm run dev\", background: true })\n→ Returns { id: \"abc123\" } - save this ID to check logs or stop it later\n\\`\\`\\`\n\n**Check on a background process:**\n\\`\\`\\`\nbash({ id: \"abc123\" }) // get full output\nbash({ id: \"abc123\", tail: 50 }) // last 50 lines only\n\\`\\`\\`\n\n**Stop a background process:**\n\\`\\`\\`\nbash({ id: \"abc123\", kill: true })\n\\`\\`\\`\n\n**Respond to interactive prompts (for yes/no questions, etc.):**\n\\`\\`\\`\nbash({ id: \"abc123\", key: \"y\" }) // send 'y' for yes\nbash({ id: \"abc123\", key: \"n\" }) // send 'n' for no \nbash({ id: \"abc123\", key: \"Enter\" }) // press Enter\nbash({ id: \"abc123\", input: \"my text\" }) // send text input\n\\`\\`\\`\n\n**IMPORTANT - Handling Interactive Commands:**\n- ALWAYS prefer non-interactive flags when available:\n - \\`npm init --yes\\` or \\`npm install --yes\\`\n - \\`npx create-next-app --yes\\` (accepts all defaults)\n - \\`npx create-react-app --yes\\`\n - \\`git commit --no-edit\\`\n - \\`apt-get install -y\\`\n- If a command might prompt for input, run it in background mode first\n- Check the output to see if it's waiting for input\n- Use \\`key: \"y\"\\` or \\`key: \"n\"\\` for yes/no prompts\n- Use \\`input: \"text\"\\` for text input prompts\n\nLogs are saved to \\`.sparkecoder/terminals/{id}/output.log\\` and can be read with \\`read_file\\` if needed.\n\n## Guidelines\n\n### Code Quality\n- Write clean, maintainable, well-documented code\n- Follow existing code style and conventions in the project\n- Use meaningful variable and function names\n- Add comments for complex logic\n\n### Problem Solving\n- Before making changes, understand the existing code structure\n- Break complex tasks into smaller, manageable steps using the todo tool\n- Test changes when possible using the bash tool\n- Handle errors gracefully and provide helpful error messages\n\n### File Operations\n- Use \\`read_file\\` to understand code before modifying\n- Use \\`write_file\\` with mode \"str_replace\" for targeted edits to existing files\n- Use \\`write_file\\` with mode \"full\" only for new files or complete rewrites\n- Always verify changes by reading files after modifications\n\n### Searching and Exploration\n${searchInstructions}\n\nFollow these principles when designing and implementing software:\n\n1. **Modularity** — Write simple parts connected by clean interfaces\n2. **Clarity** — Clarity is better than cleverness\n3. **Composition** — Design programs to be connected to other programs\n4. **Separation** — Separate policy from mechanism; separate interfaces from engines\n5. **Simplicity** — Design for simplicity; add complexity only where you must\n6. **Parsimony** — Write a big program only when it is clear by demonstration that nothing else will do\n7. **Transparency** — Design for visibility to make inspection and debugging easier\n8. **Robustness** — Robustness is the child of transparency and simplicity\n9. **Representation** — Fold knowledge into data so program logic can be stupid and robust\n10. **Least Surprise** — In interface design, always do the least surprising thing\n11. **Silence** — When a program has nothing surprising to say, it should say nothing\n12. **Repair** — When you must fail, fail noisily and as soon as possible\n13. **Economy** — Programmer time is expensive; conserve it in preference to machine time\n14. **Generation** — Avoid hand-hacking; write programs to write programs when you can\n15. **Optimization** — Prototype before polishing. Get it working before you optimize it\n16. **Diversity** — Distrust all claims for \"one true way\"\n17. **Extensibility** — Design for the future, because it will be here sooner than you think\n\n\n### Communication\n- Explain your reasoning and approach\n- Be concise but thorough\n- Ask clarifying questions when requirements are ambiguous\n- Report progress on multi-step tasks\n\n## Skills\n${skillsContext}\n\n## Current Task List\n${todosContext}\n\n${customInstructions ? `## Custom Instructions\\n${customInstructions}` : ''}\n\nRemember: You are a helpful, capable coding assistant. Take initiative, be thorough, and deliver high-quality results.`;\n\n return systemPrompt;\n}\n\n/**\n * Format todos for system prompt context\n */\nfunction formatTodosForContext(todos: TodoItem[]): string {\n if (todos.length === 0) {\n return 'No active tasks. Use the todo tool to create a plan for complex operations.';\n }\n\n const statusEmoji: Record<string, string> = {\n pending: '⬜',\n in_progress: '🔄',\n completed: '✅',\n cancelled: '❌',\n };\n\n const lines = ['Current tasks:'];\n for (const todo of todos) {\n const emoji = statusEmoji[todo.status] || '•';\n lines.push(`${emoji} [${todo.id}] ${todo.content}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Create a summary prompt for context compression\n */\nexport function createSummaryPrompt(conversationHistory: string): string {\n return `Please provide a concise summary of the following conversation history. Focus on:\n1. The main task or goal being worked on\n2. Key decisions made\n3. Important code changes or file operations performed\n4. Current state and any pending actions\n\nKeep the summary under 2000 characters while preserving essential context for continuing the work.\n\nConversation to summarize:\n${conversationHistory}\n\nSummary:`;\n}\n"],"mappings":"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","names":["generateText","tool","gateway","z","nanoid","messages","z","exec","promisify","messages","existsSync","join","nanoid","output","execAsync","promisify","exec","MAX_OUTPUT_CHARS","z","output","status","truncatedOutput","tool","z","readFile","resolve","existsSync","MAX_OUTPUT_CHARS","z","tool","resolve","existsSync","readFile","tool","z","readFile","writeFile","mkdir","resolve","relative","isAbsolute","dirname","existsSync","tool","z","z","tool","tool","z","readFile","resolve","existsSync","existsSync","resolve","readFile","z","tool","platform","messages","text","messages","gateway","generateText","tool","z","nanoid","resolve"]}
1
+ {"version":3,"sources":["../../src/agent/index.ts","../../src/db/index.ts","../../src/db/schema.ts","../../src/config/index.ts","../../src/config/types.ts","../../src/tools/bash.ts","../../src/utils/truncate.ts","../../src/terminal/tmux.ts","../../src/tools/read-file.ts","../../src/tools/write-file.ts","../../src/checkpoints/index.ts","../../src/tools/todo.ts","../../src/tools/load-skill.ts","../../src/skills/index.ts","../../src/tools/index.ts","../../src/agent/context.ts","../../src/agent/prompts.ts"],"sourcesContent":["import {\n streamText,\n generateText,\n tool,\n stepCountIs,\n type ToolSet,\n type ModelMessage,\n} from 'ai';\nimport { gateway } from '@ai-sdk/gateway';\nimport { z } from 'zod';\nimport { nanoid } from 'nanoid';\nimport {\n sessionQueries,\n toolExecutionQueries,\n Session,\n ToolExecution,\n} from '../db/index.js';\nimport { getConfig, requiresApproval, SessionConfig } from '../config/index.js';\nimport { createTools, BashToolProgress } from '../tools/index.js';\nimport { ContextManager } from './context.js';\nimport { buildSystemPrompt } from './prompts.js';\n\n// Shared store for approval resolvers (needed because approve/reject come from different HTTP requests)\nconst approvalResolvers = new Map<string, { \n resolve: (approved: boolean) => void; \n reason?: string;\n sessionId: string;\n}>();\n\nexport interface AgentOptions {\n sessionId?: string;\n name?: string;\n workingDirectory?: string;\n model?: string;\n sessionConfig?: Partial<SessionConfig>;\n}\n\nexport interface AgentRunOptions {\n prompt: string;\n abortSignal?: AbortSignal;\n /** Skip saving user message (if already saved externally) */\n skipSaveUserMessage?: boolean;\n onText?: (text: string) => void;\n onToolCall?: (toolCall: { toolCallId: string; toolName: string; input: unknown }) => void;\n onToolResult?: (result: { toolCallId: string; toolName: string; output: unknown }) => void;\n onApprovalRequired?: (execution: ToolExecution) => void;\n onStepFinish?: (step: { text?: string; toolCalls?: unknown[]; usage?: unknown }) => void;\n onAbort?: (info: { steps: unknown[] }) => void;\n /** Called when a tool (like bash) has progress to report (e.g., terminal started) */\n onToolProgress?: (progress: { toolName: string; data: BashToolProgress }) => void;\n}\n\nexport interface AgentStreamResult {\n sessionId: string;\n stream: ReturnType<typeof streamText>;\n waitForApprovals: () => Promise<ToolExecution[]>;\n /** Call this after stream completes to save response messages */\n saveResponseMessages: () => Promise<void>;\n}\n\n/**\n * The main coding agent that orchestrates LLM interactions\n */\nexport class Agent {\n private session: Session;\n private context: ContextManager;\n private baseTools: ToolSet;\n private pendingApprovals: Map<string, ToolExecution> = new Map();\n\n private constructor(session: Session, context: ContextManager, tools: ToolSet) {\n this.session = session;\n this.context = context;\n this.baseTools = tools;\n }\n\n /**\n * Create tools with optional progress callbacks\n */\n private createToolsWithCallbacks(options: {\n onToolProgress?: AgentRunOptions['onToolProgress'];\n }): ToolSet {\n const config = getConfig();\n return createTools({\n sessionId: this.session.id,\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n onBashProgress: options.onToolProgress\n ? (progress) => options.onToolProgress!({ toolName: 'bash', data: progress })\n : undefined,\n });\n }\n\n /**\n * Create or resume an agent session\n */\n static async create(options: AgentOptions = {}): Promise<Agent> {\n const config = getConfig();\n\n // Get or create session\n let session: Session;\n\n if (options.sessionId) {\n const existing = sessionQueries.getById(options.sessionId);\n if (!existing) {\n throw new Error(`Session not found: ${options.sessionId}`);\n }\n session = existing;\n } else {\n session = sessionQueries.create({\n name: options.name,\n workingDirectory: options.workingDirectory || config.resolvedWorkingDirectory,\n model: options.model || config.defaultModel,\n config: options.sessionConfig as SessionConfig,\n });\n }\n\n // Create context manager\n const context = new ContextManager({\n sessionId: session.id,\n maxContextChars: config.context?.maxChars || 200_000,\n keepRecentMessages: config.context?.keepRecentMessages || 10,\n autoSummarize: config.context?.autoSummarize ?? true,\n });\n\n // Create tools\n const tools = createTools({\n sessionId: session.id,\n workingDirectory: session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n });\n\n return new Agent(session, context, tools);\n }\n\n /**\n * Get the session ID\n */\n get sessionId(): string {\n return this.session.id;\n }\n\n /**\n * Get session details\n */\n getSession(): Session {\n return this.session;\n }\n\n /**\n * Run the agent with a prompt (streaming)\n */\n async stream(options: AgentRunOptions): Promise<AgentStreamResult> {\n const config = getConfig();\n\n // Add user message to context (skip if already saved externally)\n if (!options.skipSaveUserMessage) {\n this.context.addUserMessage(options.prompt);\n }\n\n // Update session status\n sessionQueries.updateStatus(this.session.id, 'active');\n\n // Build system prompt\n const systemPrompt = await buildSystemPrompt({\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n sessionId: this.session.id,\n });\n\n // Get conversation history\n const messages = await this.context.getMessages();\n\n // Create tools with progress callbacks if needed\n const tools = options.onToolProgress\n ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress })\n : this.baseTools;\n\n // Wrap tools with approval checking\n const wrappedTools = this.wrapToolsWithApproval(options, tools);\n\n // Create stream with reasoning enabled for supported models\n const stream = streamText({\n model: gateway(this.session.model) as any,\n system: systemPrompt,\n messages: messages as any,\n tools: wrappedTools,\n stopWhen: stepCountIs(500),\n // Forward abort signal if provided\n abortSignal: options.abortSignal,\n // Enable extended thinking/reasoning for models that support it\n providerOptions: {\n anthropic: {\n thinking: {\n type: 'enabled',\n budgetTokens: 10000,\n },\n },\n },\n onStepFinish: async (step) => {\n options.onStepFinish?.(step as any);\n },\n onAbort: ({ steps }) => {\n options.onAbort?.({ steps });\n },\n });\n\n // Helper to save response messages after stream completes\n const saveResponseMessages = async () => {\n const result = await stream;\n const response = await result.response;\n const responseMessages = response.messages as ModelMessage[];\n this.context.addResponseMessages(responseMessages);\n };\n\n return {\n sessionId: this.session.id,\n stream,\n waitForApprovals: () => this.waitForApprovals(),\n saveResponseMessages,\n };\n }\n\n /**\n * Run the agent with a prompt (non-streaming)\n */\n async run(options: Omit<AgentRunOptions, 'onText'>): Promise<{ text: string; steps: unknown[] }> {\n const config = getConfig();\n\n // Add user message to context\n this.context.addUserMessage(options.prompt);\n\n // Build system prompt\n const systemPrompt = await buildSystemPrompt({\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n sessionId: this.session.id,\n });\n\n // Get conversation history\n const messages = await this.context.getMessages();\n\n // Create tools with progress callbacks if needed\n const tools = options.onToolProgress\n ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress })\n : this.baseTools;\n\n // Wrap tools with approval checking\n const wrappedTools = this.wrapToolsWithApproval(options, tools);\n\n const result = await generateText({\n model: gateway(this.session.model) as any,\n system: systemPrompt,\n messages: messages as any,\n tools: wrappedTools,\n stopWhen: stepCountIs(500),\n // Enable extended thinking/reasoning for models that support it\n providerOptions: {\n anthropic: {\n thinking: {\n type: 'enabled',\n budgetTokens: 10000,\n },\n },\n },\n });\n\n // Save response messages using the proper AI SDK format\n const responseMessages = result.response.messages as ModelMessage[];\n this.context.addResponseMessages(responseMessages);\n\n return {\n text: result.text,\n steps: result.steps,\n };\n }\n\n /**\n * Wrap tools to add approval checking\n */\n private wrapToolsWithApproval(options: AgentRunOptions, tools?: ToolSet): ToolSet {\n const sessionConfig = this.session.config;\n const wrappedTools: ToolSet = {};\n const toolsToWrap = tools || this.baseTools;\n\n for (const [name, originalTool] of Object.entries(toolsToWrap)) {\n const needsApproval = requiresApproval(name, sessionConfig ?? undefined);\n\n if (!needsApproval) {\n wrappedTools[name] = originalTool;\n continue;\n }\n\n // Create wrapped tool that checks for approval and waits\n wrappedTools[name] = tool({\n description: originalTool.description || '',\n inputSchema: (originalTool as any).inputSchema || z.object({}),\n execute: async (input: unknown, toolOptions: { toolCallId?: string }) => {\n const toolCallId = toolOptions.toolCallId || nanoid();\n\n // Record the execution\n const execution = toolExecutionQueries.create({\n sessionId: this.session.id,\n toolName: name,\n toolCallId,\n input: input as any,\n requiresApproval: true,\n status: 'pending',\n });\n\n // Store pending approval\n this.pendingApprovals.set(toolCallId, execution);\n\n // Notify about approval requirement\n options.onApprovalRequired?.(execution);\n\n // Update session status\n sessionQueries.updateStatus(this.session.id, 'waiting');\n\n // Wait for approval decision (using shared store for cross-request access)\n const approved = await new Promise<boolean>((resolve) => {\n approvalResolvers.set(toolCallId, { resolve, sessionId: this.session.id });\n });\n\n // Get any rejection reason\n const resolverData = approvalResolvers.get(toolCallId);\n approvalResolvers.delete(toolCallId);\n this.pendingApprovals.delete(toolCallId);\n\n if (!approved) {\n // Tool was rejected\n const reason = resolverData?.reason || 'User rejected the tool execution';\n toolExecutionQueries.reject(execution.id);\n sessionQueries.updateStatus(this.session.id, 'active');\n \n return {\n status: 'rejected',\n toolCallId,\n rejected: true,\n reason,\n message: `Tool \"${name}\" was rejected by the user. Reason: ${reason}`,\n };\n }\n\n // Tool was approved - execute the original tool\n toolExecutionQueries.approve(execution.id);\n sessionQueries.updateStatus(this.session.id, 'active');\n\n try {\n const result = await (originalTool as any).execute(input, toolOptions);\n toolExecutionQueries.complete(execution.id, result);\n return result;\n } catch (error: any) {\n toolExecutionQueries.complete(execution.id, null, error.message);\n throw error;\n }\n },\n });\n }\n\n return wrappedTools;\n }\n\n /**\n * Wait for all pending approvals\n */\n async waitForApprovals(): Promise<ToolExecution[]> {\n return Array.from(this.pendingApprovals.values());\n }\n\n /**\n * Approve a pending tool execution\n */\n async approve(toolCallId: string): Promise<{ approved: true }> {\n // Check shared resolver store (the streaming Agent is waiting on this)\n const resolver = approvalResolvers.get(toolCallId);\n if (resolver) {\n resolver.resolve(true);\n return { approved: true };\n }\n\n // Fall back to database lookup\n const pendingFromDb = toolExecutionQueries.getPendingApprovals(this.session.id);\n const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);\n \n if (!execution) {\n throw new Error(`No pending approval for tool call: ${toolCallId}`);\n }\n\n // Mark as approved in DB\n toolExecutionQueries.approve(execution.id);\n return { approved: true };\n }\n\n /**\n * Reject a pending tool execution\n */\n reject(toolCallId: string, reason?: string): { rejected: true } {\n // Check shared resolver store (the streaming Agent is waiting on this)\n const resolver = approvalResolvers.get(toolCallId);\n if (resolver) {\n resolver.reason = reason;\n resolver.resolve(false);\n return { rejected: true };\n }\n\n // Fall back to database lookup\n const pendingFromDb = toolExecutionQueries.getPendingApprovals(this.session.id);\n const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);\n \n if (!execution) {\n throw new Error(`No pending approval for tool call: ${toolCallId}`);\n }\n\n // Mark as rejected in DB\n toolExecutionQueries.reject(execution.id);\n return { rejected: true };\n }\n\n /**\n * Get pending approvals\n */\n getPendingApprovals(): ToolExecution[] {\n return toolExecutionQueries.getPendingApprovals(this.session.id);\n }\n\n /**\n * Get context statistics\n */\n getContextStats() {\n return this.context.getStats();\n }\n\n /**\n * Clear conversation context (start fresh)\n */\n clearContext(): void {\n this.context.clear();\n }\n}\n\nexport { ContextManager } from './context.js';\nexport { buildSystemPrompt } from './prompts.js';\n","import Database from 'better-sqlite3';\nimport { drizzle } from 'drizzle-orm/better-sqlite3';\nimport { eq, desc, and, sql } from 'drizzle-orm';\nimport { nanoid } from 'nanoid';\nimport * as schema from './schema.js';\nimport type {\n Session,\n NewSession,\n Message,\n NewMessage,\n ToolExecution,\n NewToolExecution,\n TodoItem,\n NewTodoItem,\n SessionConfig,\n ModelMessage,\n Terminal,\n NewTerminal,\n ActiveStream,\n NewActiveStream,\n Checkpoint,\n NewCheckpoint,\n FileBackup,\n NewFileBackup,\n} from './schema.js';\n\nlet db: ReturnType<typeof drizzle<typeof schema>> | null = null;\nlet sqlite: Database.Database | null = null;\n\nexport function initDatabase(dbPath: string) {\n sqlite = new Database(dbPath);\n sqlite.pragma('journal_mode = WAL');\n db = drizzle(sqlite, { schema });\n\n // Create tables if they don't exist (preserves existing data)\n sqlite.exec(`\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n name TEXT,\n working_directory TEXT NOT NULL,\n model TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'active',\n config TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n model_message TEXT NOT NULL,\n sequence INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS tool_executions (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,\n tool_name TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n input TEXT,\n output TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n requires_approval INTEGER NOT NULL DEFAULT 0,\n error TEXT,\n started_at INTEGER NOT NULL,\n completed_at INTEGER\n );\n\n CREATE TABLE IF NOT EXISTS todo_items (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n content TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n \"order\" INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS loaded_skills (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n skill_name TEXT NOT NULL,\n loaded_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS terminals (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n name TEXT,\n command TEXT NOT NULL,\n cwd TEXT NOT NULL,\n pid INTEGER,\n status TEXT NOT NULL DEFAULT 'running',\n exit_code INTEGER,\n error TEXT,\n created_at INTEGER NOT NULL,\n stopped_at INTEGER\n );\n\n -- Table for tracking active streams (for resumable streams)\n CREATE TABLE IF NOT EXISTS active_streams (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n stream_id TEXT NOT NULL UNIQUE,\n status TEXT NOT NULL DEFAULT 'active',\n created_at INTEGER NOT NULL,\n finished_at INTEGER\n );\n\n -- Checkpoints table - created before each user message\n CREATE TABLE IF NOT EXISTS checkpoints (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n message_sequence INTEGER NOT NULL,\n git_head TEXT,\n created_at INTEGER NOT NULL\n );\n\n -- File backups table - stores original file content\n CREATE TABLE IF NOT EXISTS file_backups (\n id TEXT PRIMARY KEY,\n checkpoint_id TEXT NOT NULL REFERENCES checkpoints(id) ON DELETE CASCADE,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n file_path TEXT NOT NULL,\n original_content TEXT,\n existed INTEGER NOT NULL DEFAULT 1,\n created_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);\n CREATE INDEX IF NOT EXISTS idx_tool_executions_session ON tool_executions(session_id);\n CREATE INDEX IF NOT EXISTS idx_todo_items_session ON todo_items(session_id);\n CREATE INDEX IF NOT EXISTS idx_loaded_skills_session ON loaded_skills(session_id);\n CREATE INDEX IF NOT EXISTS idx_terminals_session ON terminals(session_id);\n CREATE INDEX IF NOT EXISTS idx_active_streams_session ON active_streams(session_id);\n CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id);\n CREATE INDEX IF NOT EXISTS idx_file_backups_checkpoint ON file_backups(checkpoint_id);\n CREATE INDEX IF NOT EXISTS idx_file_backups_session ON file_backups(session_id);\n `);\n\n return db;\n}\n\nexport function getDb() {\n if (!db) {\n throw new Error('Database not initialized. Call initDatabase first.');\n }\n return db;\n}\n\nexport function closeDatabase() {\n if (sqlite) {\n sqlite.close();\n sqlite = null;\n db = null;\n }\n}\n\n// Session queries\nexport const sessionQueries = {\n create(data: Omit<NewSession, 'id' | 'createdAt' | 'updatedAt'>): Session {\n const id = nanoid();\n const now = new Date();\n const result = getDb()\n .insert(schema.sessions)\n .values({\n id,\n ...data,\n createdAt: now,\n updatedAt: now,\n })\n .returning()\n .get();\n return result;\n },\n\n getById(id: string): Session | undefined {\n return getDb().select().from(schema.sessions).where(eq(schema.sessions.id, id)).get();\n },\n\n list(limit = 50, offset = 0): Session[] {\n return getDb()\n .select()\n .from(schema.sessions)\n .orderBy(desc(schema.sessions.createdAt))\n .limit(limit)\n .offset(offset)\n .all();\n },\n\n updateStatus(id: string, status: Session['status']): Session | undefined {\n return getDb()\n .update(schema.sessions)\n .set({ status, updatedAt: new Date() })\n .where(eq(schema.sessions.id, id))\n .returning()\n .get();\n },\n\n updateModel(id: string, model: string): Session | undefined {\n return getDb()\n .update(schema.sessions)\n .set({ model, updatedAt: new Date() })\n .where(eq(schema.sessions.id, id))\n .returning()\n .get();\n },\n\n update(id: string, updates: { model?: string; name?: string; config?: SessionConfig }): Session | undefined {\n return getDb()\n .update(schema.sessions)\n .set({ ...updates, updatedAt: new Date() })\n .where(eq(schema.sessions.id, id))\n .returning()\n .get();\n },\n\n delete(id: string): boolean {\n const result = getDb().delete(schema.sessions).where(eq(schema.sessions.id, id)).run();\n return result.changes > 0;\n },\n};\n\n// Message queries - stores AI SDK ModelMessage directly\nexport const messageQueries = {\n /**\n * Get the next sequence number for a session\n */\n getNextSequence(sessionId: string): number {\n const result = getDb()\n .select({ maxSeq: sql<number>`COALESCE(MAX(sequence), -1)` })\n .from(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .get();\n return (result?.maxSeq ?? -1) + 1;\n },\n\n /**\n * Create a single message from a ModelMessage\n */\n create(sessionId: string, modelMessage: ModelMessage): Message {\n const id = nanoid();\n const sequence = this.getNextSequence(sessionId);\n const result = getDb()\n .insert(schema.messages)\n .values({\n id,\n sessionId,\n modelMessage,\n sequence,\n createdAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n /**\n * Add multiple ModelMessages at once (from response.messages)\n * Maintains insertion order via sequence numbers\n */\n addMany(sessionId: string, modelMessages: ModelMessage[]): Message[] {\n const results: Message[] = [];\n let sequence = this.getNextSequence(sessionId);\n for (const msg of modelMessages) {\n const id = nanoid();\n const result = getDb()\n .insert(schema.messages)\n .values({\n id,\n sessionId,\n modelMessage: msg,\n sequence,\n createdAt: new Date(),\n })\n .returning()\n .get();\n results.push(result);\n sequence++;\n }\n return results;\n },\n\n /**\n * Get all messages for a session as ModelMessage[]\n * Ordered by sequence to maintain exact insertion order\n */\n getBySession(sessionId: string): Message[] {\n return getDb()\n .select()\n .from(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .orderBy(schema.messages.sequence)\n .all();\n },\n\n /**\n * Get ModelMessages directly (for passing to AI SDK)\n */\n getModelMessages(sessionId: string): ModelMessage[] {\n const messages = this.getBySession(sessionId);\n return messages.map(m => m.modelMessage);\n },\n\n getRecentBySession(sessionId: string, limit = 50): Message[] {\n return getDb()\n .select()\n .from(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .orderBy(desc(schema.messages.sequence))\n .limit(limit)\n .all()\n .reverse();\n },\n\n countBySession(sessionId: string): number {\n const result = getDb()\n .select({ count: sql<number>`count(*)` })\n .from(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .get();\n return result?.count ?? 0;\n },\n\n deleteBySession(sessionId: string): number {\n const result = getDb()\n .delete(schema.messages)\n .where(eq(schema.messages.sessionId, sessionId))\n .run();\n return result.changes;\n },\n\n /**\n * Delete all messages with sequence >= the given sequence number\n * (Used when reverting to a checkpoint)\n */\n deleteFromSequence(sessionId: string, fromSequence: number): number {\n const result = getDb()\n .delete(schema.messages)\n .where(\n and(\n eq(schema.messages.sessionId, sessionId),\n sql`sequence >= ${fromSequence}`\n )\n )\n .run();\n return result.changes;\n },\n};\n\n// Tool execution queries\nexport const toolExecutionQueries = {\n create(data: Omit<NewToolExecution, 'id' | 'startedAt'>): ToolExecution {\n const id = nanoid();\n const result = getDb()\n .insert(schema.toolExecutions)\n .values({\n id,\n ...data,\n startedAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getById(id: string): ToolExecution | undefined {\n return getDb()\n .select()\n .from(schema.toolExecutions)\n .where(eq(schema.toolExecutions.id, id))\n .get();\n },\n\n getByToolCallId(toolCallId: string): ToolExecution | undefined {\n return getDb()\n .select()\n .from(schema.toolExecutions)\n .where(eq(schema.toolExecutions.toolCallId, toolCallId))\n .get();\n },\n\n getPendingApprovals(sessionId: string): ToolExecution[] {\n return getDb()\n .select()\n .from(schema.toolExecutions)\n .where(\n and(\n eq(schema.toolExecutions.sessionId, sessionId),\n eq(schema.toolExecutions.status, 'pending'),\n eq(schema.toolExecutions.requiresApproval, true)\n )\n )\n .all();\n },\n\n approve(id: string): ToolExecution | undefined {\n return getDb()\n .update(schema.toolExecutions)\n .set({ status: 'approved' })\n .where(eq(schema.toolExecutions.id, id))\n .returning()\n .get();\n },\n\n reject(id: string): ToolExecution | undefined {\n return getDb()\n .update(schema.toolExecutions)\n .set({ status: 'rejected' })\n .where(eq(schema.toolExecutions.id, id))\n .returning()\n .get();\n },\n\n complete(\n id: string,\n output: unknown,\n error?: string\n ): ToolExecution | undefined {\n return getDb()\n .update(schema.toolExecutions)\n .set({\n status: error ? 'error' : 'completed',\n output: output as any,\n error,\n completedAt: new Date(),\n })\n .where(eq(schema.toolExecutions.id, id))\n .returning()\n .get();\n },\n\n getBySession(sessionId: string): ToolExecution[] {\n return getDb()\n .select()\n .from(schema.toolExecutions)\n .where(eq(schema.toolExecutions.sessionId, sessionId))\n .orderBy(schema.toolExecutions.startedAt)\n .all();\n },\n\n /**\n * Delete all tool executions after a given timestamp\n * (Used when reverting to a checkpoint)\n */\n deleteAfterTime(sessionId: string, afterTime: Date): number {\n const result = getDb()\n .delete(schema.toolExecutions)\n .where(\n and(\n eq(schema.toolExecutions.sessionId, sessionId),\n sql`started_at > ${afterTime.getTime()}`\n )\n )\n .run();\n return result.changes;\n },\n};\n\n// Todo item queries\nexport const todoQueries = {\n create(data: Omit<NewTodoItem, 'id' | 'createdAt' | 'updatedAt'>): TodoItem {\n const id = nanoid();\n const now = new Date();\n const result = getDb()\n .insert(schema.todoItems)\n .values({\n id,\n ...data,\n createdAt: now,\n updatedAt: now,\n })\n .returning()\n .get();\n return result;\n },\n\n createMany(\n sessionId: string,\n items: Array<{ content: string; order?: number }>\n ): TodoItem[] {\n const now = new Date();\n const values = items.map((item, index) => ({\n id: nanoid(),\n sessionId,\n content: item.content,\n order: item.order ?? index,\n createdAt: now,\n updatedAt: now,\n }));\n\n return getDb().insert(schema.todoItems).values(values).returning().all();\n },\n\n getBySession(sessionId: string): TodoItem[] {\n return getDb()\n .select()\n .from(schema.todoItems)\n .where(eq(schema.todoItems.sessionId, sessionId))\n .orderBy(schema.todoItems.order)\n .all();\n },\n\n updateStatus(\n id: string,\n status: TodoItem['status']\n ): TodoItem | undefined {\n return getDb()\n .update(schema.todoItems)\n .set({ status, updatedAt: new Date() })\n .where(eq(schema.todoItems.id, id))\n .returning()\n .get();\n },\n\n delete(id: string): boolean {\n const result = getDb()\n .delete(schema.todoItems)\n .where(eq(schema.todoItems.id, id))\n .run();\n return result.changes > 0;\n },\n\n clearSession(sessionId: string): number {\n const result = getDb()\n .delete(schema.todoItems)\n .where(eq(schema.todoItems.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\n// Loaded skills queries\nexport const skillQueries = {\n load(sessionId: string, skillName: string): schema.LoadedSkill {\n const id = nanoid();\n const result = getDb()\n .insert(schema.loadedSkills)\n .values({\n id,\n sessionId,\n skillName,\n loadedAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getBySession(sessionId: string): schema.LoadedSkill[] {\n return getDb()\n .select()\n .from(schema.loadedSkills)\n .where(eq(schema.loadedSkills.sessionId, sessionId))\n .orderBy(schema.loadedSkills.loadedAt)\n .all();\n },\n\n isLoaded(sessionId: string, skillName: string): boolean {\n const result = getDb()\n .select()\n .from(schema.loadedSkills)\n .where(\n and(\n eq(schema.loadedSkills.sessionId, sessionId),\n eq(schema.loadedSkills.skillName, skillName)\n )\n )\n .get();\n return !!result;\n },\n};\n\n// Terminal queries\nexport const terminalQueries = {\n create(data: Omit<NewTerminal, 'id' | 'createdAt'>): Terminal {\n const id = nanoid();\n const result = getDb()\n .insert(schema.terminals)\n .values({\n id,\n ...data,\n createdAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getById(id: string): Terminal | undefined {\n return getDb()\n .select()\n .from(schema.terminals)\n .where(eq(schema.terminals.id, id))\n .get();\n },\n\n getBySession(sessionId: string): Terminal[] {\n return getDb()\n .select()\n .from(schema.terminals)\n .where(eq(schema.terminals.sessionId, sessionId))\n .orderBy(desc(schema.terminals.createdAt))\n .all();\n },\n\n getRunning(sessionId: string): Terminal[] {\n return getDb()\n .select()\n .from(schema.terminals)\n .where(\n and(\n eq(schema.terminals.sessionId, sessionId),\n eq(schema.terminals.status, 'running')\n )\n )\n .all();\n },\n\n updateStatus(\n id: string,\n status: Terminal['status'],\n exitCode?: number,\n error?: string\n ): Terminal | undefined {\n return getDb()\n .update(schema.terminals)\n .set({\n status,\n exitCode,\n error,\n stoppedAt: status !== 'running' ? new Date() : undefined,\n })\n .where(eq(schema.terminals.id, id))\n .returning()\n .get();\n },\n\n updatePid(id: string, pid: number): Terminal | undefined {\n return getDb()\n .update(schema.terminals)\n .set({ pid })\n .where(eq(schema.terminals.id, id))\n .returning()\n .get();\n },\n\n delete(id: string): boolean {\n const result = getDb()\n .delete(schema.terminals)\n .where(eq(schema.terminals.id, id))\n .run();\n return result.changes > 0;\n },\n\n deleteBySession(sessionId: string): number {\n const result = getDb()\n .delete(schema.terminals)\n .where(eq(schema.terminals.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\n// Active stream queries - for resumable streams\nexport const activeStreamQueries = {\n create(sessionId: string, streamId: string): ActiveStream {\n const id = nanoid();\n const result = getDb()\n .insert(schema.activeStreams)\n .values({\n id,\n sessionId,\n streamId,\n status: 'active',\n createdAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getBySessionId(sessionId: string): ActiveStream | undefined {\n return getDb()\n .select()\n .from(schema.activeStreams)\n .where(\n and(\n eq(schema.activeStreams.sessionId, sessionId),\n eq(schema.activeStreams.status, 'active')\n )\n )\n .get();\n },\n\n getByStreamId(streamId: string): ActiveStream | undefined {\n return getDb()\n .select()\n .from(schema.activeStreams)\n .where(eq(schema.activeStreams.streamId, streamId))\n .get();\n },\n\n finish(streamId: string): ActiveStream | undefined {\n return getDb()\n .update(schema.activeStreams)\n .set({ status: 'finished', finishedAt: new Date() })\n .where(eq(schema.activeStreams.streamId, streamId))\n .returning()\n .get();\n },\n\n markError(streamId: string): ActiveStream | undefined {\n return getDb()\n .update(schema.activeStreams)\n .set({ status: 'error', finishedAt: new Date() })\n .where(eq(schema.activeStreams.streamId, streamId))\n .returning()\n .get();\n },\n\n deleteBySession(sessionId: string): number {\n const result = getDb()\n .delete(schema.activeStreams)\n .where(eq(schema.activeStreams.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\n// Checkpoint queries - for session revert functionality\nexport const checkpointQueries = {\n create(data: { sessionId: string; messageSequence: number; gitHead?: string }): Checkpoint {\n const id = nanoid();\n const result = getDb()\n .insert(schema.checkpoints)\n .values({\n id,\n sessionId: data.sessionId,\n messageSequence: data.messageSequence,\n gitHead: data.gitHead,\n createdAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getById(id: string): Checkpoint | undefined {\n return getDb()\n .select()\n .from(schema.checkpoints)\n .where(eq(schema.checkpoints.id, id))\n .get();\n },\n\n getBySession(sessionId: string): Checkpoint[] {\n return getDb()\n .select()\n .from(schema.checkpoints)\n .where(eq(schema.checkpoints.sessionId, sessionId))\n .orderBy(schema.checkpoints.messageSequence)\n .all();\n },\n\n getByMessageSequence(sessionId: string, messageSequence: number): Checkpoint | undefined {\n return getDb()\n .select()\n .from(schema.checkpoints)\n .where(\n and(\n eq(schema.checkpoints.sessionId, sessionId),\n eq(schema.checkpoints.messageSequence, messageSequence)\n )\n )\n .get();\n },\n\n getLatest(sessionId: string): Checkpoint | undefined {\n return getDb()\n .select()\n .from(schema.checkpoints)\n .where(eq(schema.checkpoints.sessionId, sessionId))\n .orderBy(desc(schema.checkpoints.messageSequence))\n .limit(1)\n .get();\n },\n\n /**\n * Delete all checkpoints after a given sequence number\n * (Used when reverting to a checkpoint)\n */\n deleteAfterSequence(sessionId: string, messageSequence: number): number {\n const result = getDb()\n .delete(schema.checkpoints)\n .where(\n and(\n eq(schema.checkpoints.sessionId, sessionId),\n sql`message_sequence > ${messageSequence}`\n )\n )\n .run();\n return result.changes;\n },\n\n deleteBySession(sessionId: string): number {\n const result = getDb()\n .delete(schema.checkpoints)\n .where(eq(schema.checkpoints.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\n// File backup queries - for storing original file content\nexport const fileBackupQueries = {\n create(data: {\n checkpointId: string;\n sessionId: string;\n filePath: string;\n originalContent: string | null;\n existed: boolean;\n }): FileBackup {\n const id = nanoid();\n const result = getDb()\n .insert(schema.fileBackups)\n .values({\n id,\n checkpointId: data.checkpointId,\n sessionId: data.sessionId,\n filePath: data.filePath,\n originalContent: data.originalContent,\n existed: data.existed,\n createdAt: new Date(),\n })\n .returning()\n .get();\n return result;\n },\n\n getByCheckpoint(checkpointId: string): FileBackup[] {\n return getDb()\n .select()\n .from(schema.fileBackups)\n .where(eq(schema.fileBackups.checkpointId, checkpointId))\n .all();\n },\n\n getBySession(sessionId: string): FileBackup[] {\n return getDb()\n .select()\n .from(schema.fileBackups)\n .where(eq(schema.fileBackups.sessionId, sessionId))\n .orderBy(schema.fileBackups.createdAt)\n .all();\n },\n\n /**\n * Get all file backups from a given checkpoint sequence onwards (inclusive)\n * (Used when reverting - need to restore these files)\n * \n * When reverting to checkpoint X, we need backups from checkpoint X and all later ones\n * because checkpoint X's backups represent the state BEFORE processing message X.\n */\n getFromSequence(sessionId: string, messageSequence: number): FileBackup[] {\n // Get all checkpoints from this sequence onwards, then get their backups\n const checkpointsFrom = getDb()\n .select()\n .from(schema.checkpoints)\n .where(\n and(\n eq(schema.checkpoints.sessionId, sessionId),\n sql`message_sequence >= ${messageSequence}`\n )\n )\n .all();\n\n if (checkpointsFrom.length === 0) {\n return [];\n }\n\n const checkpointIds = checkpointsFrom.map(c => c.id);\n \n // Get all backups for these checkpoints\n const allBackups: FileBackup[] = [];\n for (const cpId of checkpointIds) {\n const backups = getDb()\n .select()\n .from(schema.fileBackups)\n .where(eq(schema.fileBackups.checkpointId, cpId))\n .all();\n allBackups.push(...backups);\n }\n \n return allBackups;\n },\n\n /**\n * Check if a file already has a backup in the current checkpoint\n */\n hasBackup(checkpointId: string, filePath: string): boolean {\n const result = getDb()\n .select()\n .from(schema.fileBackups)\n .where(\n and(\n eq(schema.fileBackups.checkpointId, checkpointId),\n eq(schema.fileBackups.filePath, filePath)\n )\n )\n .get();\n return !!result;\n },\n\n deleteBySession(sessionId: string): number {\n const result = getDb()\n .delete(schema.fileBackups)\n .where(eq(schema.fileBackups.sessionId, sessionId))\n .run();\n return result.changes;\n },\n};\n\nexport type { \n Session, \n Message, \n ToolExecution, \n TodoItem, \n SessionConfig, \n ModelMessage,\n Terminal,\n ActiveStream,\n Checkpoint,\n FileBackup,\n};\n","import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';\n\n// Sessions table - represents an agent session/thread\nexport const sessions = sqliteTable('sessions', {\n id: text('id').primaryKey(),\n name: text('name'),\n workingDirectory: text('working_directory').notNull(),\n model: text('model').notNull(),\n status: text('status', { enum: ['active', 'waiting', 'completed', 'error'] })\n .notNull()\n .default('active'),\n config: text('config', { mode: 'json' }).$type<SessionConfig>(),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n updatedAt: integer('updated_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Messages table - stores AI SDK ModelMessage directly\nexport const messages = sqliteTable('messages', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n // Store the entire ModelMessage as JSON (role + content)\n modelMessage: text('model_message', { mode: 'json' }).$type<ModelMessage>().notNull(),\n // Sequence number within session to maintain exact ordering\n sequence: integer('sequence').notNull().default(0),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Tool executions - tracks all tool calls and their results\nexport const toolExecutions = sqliteTable('tool_executions', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n messageId: text('message_id').references(() => messages.id, { onDelete: 'cascade' }),\n toolName: text('tool_name').notNull(),\n toolCallId: text('tool_call_id').notNull(),\n input: text('input', { mode: 'json' }),\n output: text('output', { mode: 'json' }),\n status: text('status', { enum: ['pending', 'approved', 'rejected', 'completed', 'error'] })\n .notNull()\n .default('pending'),\n requiresApproval: integer('requires_approval', { mode: 'boolean' }).notNull().default(false),\n error: text('error'),\n startedAt: integer('started_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n completedAt: integer('completed_at', { mode: 'timestamp' }),\n});\n\n// Todo items for the planning tool\nexport const todoItems = sqliteTable('todo_items', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n content: text('content').notNull(),\n status: text('status', { enum: ['pending', 'in_progress', 'completed', 'cancelled'] })\n .notNull()\n .default('pending'),\n order: integer('order').notNull().default(0),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n updatedAt: integer('updated_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Skills loaded into sessions\nexport const loadedSkills = sqliteTable('loaded_skills', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n skillName: text('skill_name').notNull(),\n loadedAt: integer('loaded_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Terminal sessions - background processes managed by agents\nexport const terminals = sqliteTable('terminals', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n name: text('name'), // Optional friendly name (e.g., \"dev-server\")\n command: text('command').notNull(), // The command that was run\n cwd: text('cwd').notNull(), // Working directory\n pid: integer('pid'), // Process ID (null if not running)\n status: text('status', { enum: ['running', 'stopped', 'error'] })\n .notNull()\n .default('running'),\n exitCode: integer('exit_code'), // Exit code if stopped\n error: text('error'), // Error message if status is 'error'\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n stoppedAt: integer('stopped_at', { mode: 'timestamp' }),\n});\n\n// Active streams - tracks resumable stream sessions for multi-client sync\nexport const activeStreams = sqliteTable('active_streams', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n streamId: text('stream_id').notNull().unique(), // Unique stream identifier\n status: text('status', { enum: ['active', 'finished', 'error'] })\n .notNull()\n .default('active'),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n finishedAt: integer('finished_at', { mode: 'timestamp' }),\n});\n\n// Checkpoints - created before each user message for revert capability\nexport const checkpoints = sqliteTable('checkpoints', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n // The message sequence number this checkpoint was created BEFORE\n // (i.e., the state before this user message was processed)\n messageSequence: integer('message_sequence').notNull(),\n // Optional git commit hash if in a git repo\n gitHead: text('git_head'),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// File backups - stores original file content before modifications\nexport const fileBackups = sqliteTable('file_backups', {\n id: text('id').primaryKey(),\n checkpointId: text('checkpoint_id')\n .notNull()\n .references(() => checkpoints.id, { onDelete: 'cascade' }),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n // Relative path from working directory\n filePath: text('file_path').notNull(),\n // Original content (null means file didn't exist before)\n originalContent: text('original_content'),\n // Whether the file existed before this checkpoint\n existed: integer('existed', { mode: 'boolean' }).notNull().default(true),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n});\n\n// Types for JSON columns\nexport interface SessionConfig {\n toolApprovals?: Record<string, boolean>;\n approvalWebhook?: string;\n skillsDirectory?: string;\n maxContextChars?: number;\n}\n\n// AI SDK ModelMessage types - stored directly for accurate context passing\n// These match the exact format from AI SDK's response.messages\nexport type ModelMessage = \n | SystemModelMessage\n | UserModelMessage\n | AssistantModelMessage\n | ToolModelMessage;\n\nexport interface SystemModelMessage {\n role: 'system';\n content: string;\n}\n\nexport interface UserModelMessage {\n role: 'user';\n content: string;\n}\n\nexport interface AssistantModelMessage {\n role: 'assistant';\n content: string | AssistantContentPart[];\n}\n\nexport interface ToolModelMessage {\n role: 'tool';\n content: ToolResultPart[];\n}\n\nexport interface AssistantContentPart {\n type: 'text' | 'tool-call' | 'reasoning';\n text?: string;\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n}\n\nexport interface ToolResultPart {\n type: 'tool-result';\n toolCallId: string;\n toolName: string;\n output: unknown;\n}\n\n// Type exports for queries\nexport type Session = typeof sessions.$inferSelect;\nexport type NewSession = typeof sessions.$inferInsert;\nexport type Message = typeof messages.$inferSelect;\nexport type NewMessage = typeof messages.$inferInsert;\nexport type ToolExecution = typeof toolExecutions.$inferSelect;\nexport type NewToolExecution = typeof toolExecutions.$inferInsert;\nexport type TodoItem = typeof todoItems.$inferSelect;\nexport type NewTodoItem = typeof todoItems.$inferInsert;\nexport type LoadedSkill = typeof loadedSkills.$inferSelect;\nexport type Terminal = typeof terminals.$inferSelect;\nexport type NewTerminal = typeof terminals.$inferInsert;\nexport type ActiveStream = typeof activeStreams.$inferSelect;\nexport type NewActiveStream = typeof activeStreams.$inferInsert;\nexport type Checkpoint = typeof checkpoints.$inferSelect;\nexport type NewCheckpoint = typeof checkpoints.$inferInsert;\nexport type FileBackup = typeof fileBackups.$inferSelect;\nexport type NewFileBackup = typeof fileBackups.$inferInsert;","import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve, dirname, join } from 'node:path';\nimport { homedir, platform } from 'node:os';\nimport {\n SparkcoderConfig,\n SparkcoderConfigSchema,\n ResolvedConfig,\n} from './types.js';\n\nconst CONFIG_FILE_NAMES = [\n 'sparkecoder.config.json',\n 'sparkecoder.json',\n '.sparkecoder.json',\n];\n\n/**\n * Get the standard application data directory for the current OS\n * - macOS: ~/Library/Application Support/sparkecoder\n * - Windows: %APPDATA%/sparkecoder\n * - Linux: ~/.local/share/sparkecoder\n */\nexport function getAppDataDirectory(): string {\n const appName = 'sparkecoder';\n \n switch (platform()) {\n case 'darwin':\n return join(homedir(), 'Library', 'Application Support', appName);\n case 'win32':\n return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), appName);\n default:\n // Linux and other Unix-like systems\n return join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), appName);\n }\n}\n\n/**\n * Ensure the app data directory exists\n */\nexport function ensureAppDataDirectory(): string {\n const dir = getAppDataDirectory();\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\nlet cachedConfig: ResolvedConfig | null = null;\n\n/**\n * Find the config file by searching:\n * 1. Up the directory tree from startDir (project-specific config)\n * 2. In the app data directory (global config)\n */\nfunction findConfigFile(startDir: string): string | null {\n // First, search up the directory tree\n let currentDir = startDir;\n\n while (currentDir !== dirname(currentDir)) {\n for (const fileName of CONFIG_FILE_NAMES) {\n const configPath = resolve(currentDir, fileName);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n currentDir = dirname(currentDir);\n }\n\n // If not found, check the app data directory for a global config\n const appDataDir = getAppDataDirectory();\n for (const fileName of CONFIG_FILE_NAMES) {\n const configPath = join(appDataDir, fileName);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n\n return null;\n}\n\n/**\n * Load and parse the config file\n */\nexport function loadConfig(\n configPath?: string,\n workingDirectory?: string\n): ResolvedConfig {\n const cwd = workingDirectory || process.cwd();\n\n // Try to find config file\n let rawConfig: Partial<SparkcoderConfig> = {};\n let configDir = cwd;\n\n if (configPath) {\n if (!existsSync(configPath)) {\n throw new Error(`Config file not found: ${configPath}`);\n }\n const content = readFileSync(configPath, 'utf-8');\n rawConfig = JSON.parse(content);\n configDir = dirname(resolve(configPath));\n } else {\n const foundPath = findConfigFile(cwd);\n if (foundPath) {\n const content = readFileSync(foundPath, 'utf-8');\n rawConfig = JSON.parse(content);\n configDir = dirname(foundPath);\n }\n }\n\n // Override with environment variables\n if (process.env.SPARKECODER_MODEL) {\n rawConfig.defaultModel = process.env.SPARKECODER_MODEL;\n }\n if (process.env.SPARKECODER_PORT) {\n rawConfig.server = {\n port: parseInt(process.env.SPARKECODER_PORT, 10),\n host: rawConfig.server?.host ?? '127.0.0.1',\n };\n }\n if (process.env.DATABASE_PATH) {\n rawConfig.databasePath = process.env.DATABASE_PATH;\n }\n\n // Parse and validate\n const config = SparkcoderConfigSchema.parse(rawConfig);\n\n // Resolve working directory\n // Priority: CLI argument > absolute path in config > current working directory\n // Note: workingDirectory in config is only used if it's an absolute path,\n // otherwise we default to where the CLI was run from\n let resolvedWorkingDirectory: string;\n if (workingDirectory) {\n // Explicitly passed via CLI\n resolvedWorkingDirectory = workingDirectory;\n } else if (config.workingDirectory && config.workingDirectory !== '.' && config.workingDirectory.startsWith('/')) {\n // Absolute path in config\n resolvedWorkingDirectory = config.workingDirectory;\n } else {\n // Default to current working directory (where CLI was run)\n resolvedWorkingDirectory = process.cwd();\n }\n\n const resolvedSkillsDirectories = [\n resolve(configDir, config.skills?.directory || './skills'),\n // Built-in skills\n resolve(dirname(import.meta.url.replace('file://', '')), '../skills/default'),\n ...(config.skills?.additionalDirectories || []).map((dir) =>\n resolve(configDir, dir)\n ),\n ].filter((dir) => {\n try {\n return existsSync(dir);\n } catch {\n return false;\n }\n });\n\n // Use app data directory for database by default, unless explicitly configured\n let resolvedDatabasePath: string;\n if (config.databasePath && config.databasePath !== './sparkecoder.db') {\n // User explicitly set a custom path\n resolvedDatabasePath = resolve(configDir, config.databasePath);\n } else {\n // Use standard OS app data directory\n const appDataDir = ensureAppDataDirectory();\n resolvedDatabasePath = join(appDataDir, 'sparkecoder.db');\n }\n\n const resolved: ResolvedConfig = {\n ...config,\n server: {\n port: config.server.port,\n host: config.server.host ?? '127.0.0.1',\n },\n resolvedWorkingDirectory,\n resolvedSkillsDirectories,\n resolvedDatabasePath,\n };\n\n cachedConfig = resolved;\n return resolved;\n}\n\n/**\n * Get the cached config (must call loadConfig first)\n */\nexport function getConfig(): ResolvedConfig {\n if (!cachedConfig) {\n throw new Error('Config not loaded. Call loadConfig first.');\n }\n return cachedConfig;\n}\n\n/**\n * Check if a tool requires approval\n */\nexport function requiresApproval(\n toolName: string,\n sessionConfig?: { toolApprovals?: Record<string, boolean> }\n): boolean {\n const config = getConfig();\n\n // Session-level override takes precedence\n if (sessionConfig?.toolApprovals?.[toolName] !== undefined) {\n return sessionConfig.toolApprovals[toolName];\n }\n\n // Check global config\n const globalApprovals = config.toolApprovals as Record<string, boolean>;\n if (globalApprovals[toolName] !== undefined) {\n return globalApprovals[toolName];\n }\n\n // Default: bash requires approval, others don't\n if (toolName === 'bash') {\n return true;\n }\n\n return false;\n}\n\n/**\n * Create a default config file\n */\nexport function createDefaultConfig(): SparkcoderConfig {\n return {\n defaultModel: 'anthropic/claude-opus-4-5',\n // workingDirectory is intentionally not set - defaults to where CLI is run\n toolApprovals: {\n bash: true,\n write_file: false,\n read_file: false,\n load_skill: false,\n todo: false,\n },\n skills: {\n directory: './skills',\n additionalDirectories: [],\n },\n context: {\n maxChars: 200_000,\n autoSummarize: true,\n keepRecentMessages: 10,\n },\n server: {\n port: 3141,\n host: '127.0.0.1',\n },\n databasePath: './sparkecoder.db',\n };\n}\n\n// ============================================\n// API Key Management\n// ============================================\n\nconst API_KEYS_FILE = 'api-keys.json';\n\n// Provider to environment variable mapping\nconst PROVIDER_ENV_MAP: Record<string, string> = {\n anthropic: 'ANTHROPIC_API_KEY',\n openai: 'OPENAI_API_KEY',\n google: 'GOOGLE_GENERATIVE_AI_API_KEY',\n xai: 'XAI_API_KEY',\n 'ai-gateway': 'AI_GATEWAY_API_KEY',\n};\n\n// All supported providers\nexport const SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);\n\ninterface StoredApiKeys {\n [provider: string]: string;\n}\n\n/**\n * Get the path to the API keys file\n */\nfunction getApiKeysPath(): string {\n const appDir = ensureAppDataDirectory();\n return join(appDir, API_KEYS_FILE);\n}\n\n/**\n * Load stored API keys from file\n */\nfunction loadStoredApiKeys(): StoredApiKeys {\n const keysPath = getApiKeysPath();\n if (!existsSync(keysPath)) {\n return {};\n }\n try {\n const content = readFileSync(keysPath, 'utf-8');\n return JSON.parse(content);\n } catch {\n return {};\n }\n}\n\n/**\n * Save API keys to file\n */\nfunction saveStoredApiKeys(keys: StoredApiKeys): void {\n const keysPath = getApiKeysPath();\n writeFileSync(keysPath, JSON.stringify(keys, null, 2), { mode: 0o600 }); // Secure permissions\n}\n\n/**\n * Load API keys from storage into environment variables\n * Called on startup\n */\nexport function loadApiKeysIntoEnv(): void {\n const storedKeys = loadStoredApiKeys();\n \n for (const [provider, envVar] of Object.entries(PROVIDER_ENV_MAP)) {\n // Only set if not already in env (env takes precedence)\n if (!process.env[envVar] && storedKeys[provider]) {\n process.env[envVar] = storedKeys[provider];\n }\n }\n}\n\n/**\n * Set an API key for a provider\n * Saves to storage and sets in current process env\n */\nexport function setApiKey(provider: string, apiKey: string): void {\n const normalizedProvider = provider.toLowerCase();\n const envVar = PROVIDER_ENV_MAP[normalizedProvider];\n \n if (!envVar) {\n throw new Error(`Unknown provider: ${provider}. Supported: ${SUPPORTED_PROVIDERS.join(', ')}`);\n }\n \n // Save to storage\n const storedKeys = loadStoredApiKeys();\n storedKeys[normalizedProvider] = apiKey;\n saveStoredApiKeys(storedKeys);\n \n // Set in current process\n process.env[envVar] = apiKey;\n}\n\n/**\n * Remove an API key for a provider\n */\nexport function removeApiKey(provider: string): void {\n const normalizedProvider = provider.toLowerCase();\n const envVar = PROVIDER_ENV_MAP[normalizedProvider];\n \n if (!envVar) {\n throw new Error(`Unknown provider: ${provider}. Supported: ${SUPPORTED_PROVIDERS.join(', ')}`);\n }\n \n // Remove from storage\n const storedKeys = loadStoredApiKeys();\n delete storedKeys[normalizedProvider];\n saveStoredApiKeys(storedKeys);\n \n // Remove from current process (if it was from storage)\n // Note: We can't know if it was from env or storage, so we don't remove from env\n}\n\n/**\n * Get API key status for all providers\n * Returns masked keys (first 4 and last 4 chars) and source (env/storage/none)\n */\nexport function getApiKeyStatus(): Array<{\n provider: string;\n envVar: string;\n configured: boolean;\n source: 'env' | 'storage' | 'none';\n maskedKey: string | null;\n}> {\n const storedKeys = loadStoredApiKeys();\n \n return SUPPORTED_PROVIDERS.map((provider) => {\n const envVar = PROVIDER_ENV_MAP[provider];\n const envValue = process.env[envVar];\n const storedValue = storedKeys[provider];\n \n let source: 'env' | 'storage' | 'none' = 'none';\n let value: string | undefined;\n \n if (envValue) {\n // Check if it came from storage (by comparing)\n if (storedValue && envValue === storedValue) {\n source = 'storage';\n } else {\n source = 'env';\n }\n value = envValue;\n } else if (storedValue) {\n source = 'storage';\n value = storedValue;\n }\n \n return {\n provider,\n envVar,\n configured: !!value,\n source,\n maskedKey: value ? maskApiKey(value) : null,\n };\n });\n}\n\n/**\n * Mask an API key for display (show first 4 and last 4 chars)\n */\nfunction maskApiKey(key: string): string {\n if (key.length <= 12) {\n return '****' + key.slice(-4);\n }\n return key.slice(0, 4) + '...' + key.slice(-4);\n}\n\nexport * from './types.js';\n","import { z } from 'zod';\n\n// Tool approval configuration\nexport const ToolApprovalConfigSchema = z.object({\n bash: z.boolean().optional().default(true),\n write_file: z.boolean().optional().default(false),\n read_file: z.boolean().optional().default(false),\n load_skill: z.boolean().optional().default(false),\n todo: z.boolean().optional().default(false),\n});\n\n// Skill definition (from frontmatter)\nexport const SkillMetadataSchema = z.object({\n name: z.string(),\n description: z.string(),\n});\n\n// Session-specific config (stored in DB)\nexport const SessionConfigSchema = z.object({\n toolApprovals: z.record(z.string(), z.boolean()).optional(),\n approvalWebhook: z.string().url().optional(),\n skillsDirectory: z.string().optional(),\n maxContextChars: z.number().optional().default(200_000),\n});\n\n// Main sparkecoder config file schema\nexport const SparkcoderConfigSchema = z.object({\n // Default model to use (Vercel AI Gateway format)\n defaultModel: z.string().default('anthropic/claude-opus-4-5'),\n\n // Working directory for file operations\n workingDirectory: z.string().optional(),\n\n // Tool approval settings\n toolApprovals: ToolApprovalConfigSchema.optional().default({}),\n\n // Approval webhook URL (called when approval is needed)\n approvalWebhook: z.string().url().optional(),\n\n // Skills configuration\n skills: z\n .object({\n // Directory containing skill files\n directory: z.string().optional().default('./skills'),\n // Additional skill directories to include\n additionalDirectories: z.array(z.string()).optional().default([]),\n })\n .optional()\n .default({}),\n\n // Context management\n context: z\n .object({\n // Maximum context size before summarization (in characters)\n maxChars: z.number().optional().default(200_000),\n // Enable automatic summarization\n autoSummarize: z.boolean().optional().default(true),\n // Number of recent messages to keep after summarization\n keepRecentMessages: z.number().optional().default(10),\n })\n .optional()\n .default({}),\n\n // Server configuration\n server: z\n .object({\n port: z.number().default(3141),\n host: z.string().default('127.0.0.1'),\n })\n .default({ port: 3141, host: '127.0.0.1' }),\n\n // Database path\n databasePath: z.string().optional().default('./sparkecoder.db'),\n});\n\nexport type ToolApprovalConfig = z.infer<typeof ToolApprovalConfigSchema>;\nexport type SkillMetadata = z.infer<typeof SkillMetadataSchema>;\nexport type SessionConfig = z.infer<typeof SessionConfigSchema>;\nexport type SparkcoderConfig = z.infer<typeof SparkcoderConfigSchema>;\n\n// Runtime config with resolved paths\nexport interface ResolvedConfig extends Omit<SparkcoderConfig, 'server'> {\n server: {\n port: number;\n host: string;\n };\n resolvedWorkingDirectory: string;\n resolvedSkillsDirectories: string[];\n resolvedDatabasePath: string;\n}\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { truncateOutput } from '../utils/truncate.js';\nimport * as tmux from '../terminal/tmux.js';\n\nconst execAsync = promisify(exec);\n\nconst COMMAND_TIMEOUT = 120_000; // 2 minutes for sync commands\nconst MAX_OUTPUT_CHARS = 10_000;\n\n// Commands that are blocked for safety\nconst BLOCKED_COMMANDS = [\n 'rm -rf /',\n 'rm -rf ~',\n 'mkfs',\n 'dd if=/dev/zero',\n ':(){:|:&};:',\n 'chmod -R 777 /',\n];\n\n/**\n * Check if a command is blocked\n */\nfunction isBlockedCommand(command: string): boolean {\n const normalizedCommand = command.toLowerCase().trim();\n return BLOCKED_COMMANDS.some((blocked) =>\n normalizedCommand.includes(blocked.toLowerCase())\n );\n}\n\nexport interface BashToolProgress {\n terminalId: string;\n status: 'started' | 'running' | 'completed';\n command?: string;\n}\n\nexport interface BashToolOptions {\n workingDirectory: string;\n sessionId: string;\n onOutput?: (output: string) => void;\n onProgress?: (progress: BashToolProgress) => void;\n}\n\n// Unified bash tool schema - Option A (minimal flags)\nconst bashInputSchema = z.object({\n command: z\n .string()\n .optional()\n .describe('The command to execute. Required for running new commands.'),\n background: z\n .boolean()\n .default(false)\n .describe('Run the command in background mode (for dev servers, watchers). Returns immediately with terminal ID.'),\n id: z\n .string()\n .optional()\n .describe('Terminal ID. Use to get logs from, send input to, or kill an existing terminal.'),\n kill: z\n .boolean()\n .optional()\n .describe('Kill the terminal with the given ID.'),\n tail: z\n .number()\n .optional()\n .describe('Number of lines to return from the end of output (for logs).'),\n input: z\n .string()\n .optional()\n .describe('Send text input to an interactive terminal (requires id). Used for responding to prompts.'),\n key: z\n .enum(['Enter', 'Escape', 'Up', 'Down', 'Left', 'Right', 'Tab', 'C-c', 'C-d', 'y', 'n'])\n .optional()\n .describe('Send a special key to an interactive terminal (requires id). Use \"y\" or \"n\" for yes/no prompts.'),\n});\n\ntype BashInput = z.infer<typeof bashInputSchema>;\n\n// Cache tmux availability at startup\nlet useTmux: boolean | null = null;\n\nasync function shouldUseTmux(): Promise<boolean> {\n if (useTmux === null) {\n useTmux = await tmux.isTmuxAvailable();\n if (!useTmux) {\n console.warn('[bash] tmux not available, using fallback exec mode');\n }\n }\n return useTmux;\n}\n\n/**\n * Fallback implementation using exec (when tmux is not available)\n */\nasync function execFallback(\n command: string,\n workingDirectory: string,\n onOutput?: (output: string) => void\n): Promise<{ success: boolean; output: string; exitCode: number; error?: string }> {\n try {\n const { stdout, stderr } = await execAsync(command, {\n cwd: workingDirectory,\n timeout: COMMAND_TIMEOUT,\n maxBuffer: 10 * 1024 * 1024,\n });\n\n const output = truncateOutput(stdout + (stderr ? `\\n${stderr}` : ''), MAX_OUTPUT_CHARS);\n onOutput?.(output);\n\n return {\n success: true,\n output,\n exitCode: 0,\n };\n } catch (error: any) {\n const output = truncateOutput(\n (error.stdout || '') + (error.stderr ? `\\n${error.stderr}` : ''),\n MAX_OUTPUT_CHARS\n );\n onOutput?.(output || error.message);\n\n if (error.killed) {\n return {\n success: false,\n error: `Command timed out after ${COMMAND_TIMEOUT / 1000} seconds`,\n output,\n exitCode: 124,\n };\n }\n\n return {\n success: false,\n error: error.message,\n output,\n exitCode: error.code ?? 1,\n };\n }\n}\n\nexport function createBashTool(options: BashToolOptions) {\n return tool({\n description: `Execute commands in the terminal. Every command runs in its own session with logs saved to disk.\n\n**Run a command (default - waits for completion):**\nbash({ command: \"npm install\" })\nbash({ command: \"git status\" })\n\n**Run in background (for dev servers, watchers, or interactive commands):**\nbash({ command: \"npm run dev\", background: true })\n→ Returns { id: \"abc123\" } - save this ID\n\n**Check on a background process:**\nbash({ id: \"abc123\" })\nbash({ id: \"abc123\", tail: 50 }) // last 50 lines only\n\n**Stop a background process:**\nbash({ id: \"abc123\", kill: true })\n\n**Respond to interactive prompts (for yes/no questions, etc.):**\nbash({ id: \"abc123\", key: \"y\" }) // send 'y' for yes\nbash({ id: \"abc123\", key: \"n\" }) // send 'n' for no\nbash({ id: \"abc123\", key: \"Enter\" }) // press Enter\nbash({ id: \"abc123\", input: \"my text\" }) // send text input\n\n**IMPORTANT for interactive commands:**\n- Use --yes, -y, or similar flags to avoid prompts when available\n- For create-next-app: add --yes to accept defaults\n- For npm: add --yes or -y to skip confirmation\n- If prompts are unavoidable, run in background mode and use input/key to respond\n\nLogs are saved to .sparkecoder/terminals/{id}/output.log`,\n\n inputSchema: bashInputSchema,\n\n execute: async (inputArgs: BashInput) => {\n const { command, background, id, kill, tail, input: textInput, key } = inputArgs;\n\n // Handle terminal management (id-based operations)\n if (id) {\n // Kill a terminal\n if (kill) {\n const success = await tmux.killTerminal(id);\n return {\n success,\n id,\n status: success ? 'stopped' : 'not_found',\n message: success ? `Terminal ${id} stopped` : `Terminal ${id} not found or already stopped`,\n };\n }\n\n // Send input to an interactive terminal\n if (textInput !== undefined) {\n const success = await tmux.sendInput(id, textInput, { pressEnter: true });\n if (!success) {\n return {\n success: false,\n id,\n error: `Terminal ${id} not found or not running`,\n };\n }\n \n // Wait a moment for the input to be processed, then get logs\n await new Promise(r => setTimeout(r, 300));\n const { output, status } = await tmux.getLogs(id, options.workingDirectory, { tail: tail || 50, sessionId: options.sessionId });\n const truncatedOutput = truncateOutput(output, MAX_OUTPUT_CHARS);\n \n return {\n success: true,\n id,\n output: truncatedOutput,\n status,\n message: `Sent input \"${textInput}\" to terminal`,\n };\n }\n\n // Send a special key to an interactive terminal\n if (key) {\n const success = await tmux.sendKey(id, key);\n if (!success) {\n return {\n success: false,\n id,\n error: `Terminal ${id} not found or not running`,\n };\n }\n \n // Wait a moment for the key to be processed, then get logs\n await new Promise(r => setTimeout(r, 300));\n const { output, status } = await tmux.getLogs(id, options.workingDirectory, { tail: tail || 50, sessionId: options.sessionId });\n const truncatedOutput = truncateOutput(output, MAX_OUTPUT_CHARS);\n \n return {\n success: true,\n id,\n output: truncatedOutput,\n status,\n message: `Sent key \"${key}\" to terminal`,\n };\n }\n\n // Get logs/status from a terminal\n const { output, status } = await tmux.getLogs(id, options.workingDirectory, { tail, sessionId: options.sessionId });\n const truncatedOutput = truncateOutput(output, MAX_OUTPUT_CHARS);\n\n return {\n success: true,\n id,\n output: truncatedOutput,\n status,\n };\n }\n\n // Running a new command requires the command parameter\n if (!command) {\n return {\n success: false,\n error: 'Either \"command\" (to run a new command) or \"id\" (to check/kill/send input) is required',\n };\n }\n\n // Safety check\n if (isBlockedCommand(command)) {\n return {\n success: false,\n error: 'This command is blocked for safety reasons.',\n output: '',\n exitCode: 1,\n };\n }\n\n // Check if we can use tmux\n const canUseTmux = await shouldUseTmux();\n\n if (background) {\n // Background mode\n if (!canUseTmux) {\n return {\n success: false,\n error: 'Background mode requires tmux to be installed. Install with: brew install tmux (macOS) or apt install tmux (Linux)',\n };\n }\n\n // Generate terminal ID upfront and emit progress\n const terminalId = tmux.generateTerminalId();\n options.onProgress?.({ terminalId, status: 'started', command });\n\n const result = await tmux.runBackground(command, options.workingDirectory, {\n sessionId: options.sessionId,\n terminalId,\n });\n\n return {\n success: true,\n id: result.id,\n status: 'running',\n message: `Started background process. Use bash({ id: \"${result.id}\" }) to check logs.`,\n };\n }\n\n // Sync mode (default)\n if (canUseTmux) {\n // Use tmux for sync mode too (unified infrastructure)\n // Generate terminal ID upfront and emit progress so UI can stream output\n const terminalId = tmux.generateTerminalId();\n options.onProgress?.({ terminalId, status: 'started', command });\n\n try {\n const result = await tmux.runSync(command, options.workingDirectory, {\n sessionId: options.sessionId,\n timeout: COMMAND_TIMEOUT,\n terminalId,\n });\n\n const truncatedOutput = truncateOutput(result.output, MAX_OUTPUT_CHARS);\n options.onOutput?.(truncatedOutput);\n\n // Emit completed status\n options.onProgress?.({ terminalId, status: 'completed', command });\n\n return {\n success: result.exitCode === 0,\n id: result.id,\n output: truncatedOutput,\n exitCode: result.exitCode,\n status: result.status,\n };\n } catch (error: any) {\n options.onProgress?.({ terminalId, status: 'completed', command });\n return {\n success: false,\n error: error.message,\n output: '',\n exitCode: 1,\n };\n }\n } else {\n // Fallback to exec (no tmux)\n const result = await execFallback(command, options.workingDirectory, options.onOutput);\n return {\n success: result.success,\n output: result.output,\n exitCode: result.exitCode,\n error: result.error,\n };\n }\n },\n });\n}\n\nexport type BashTool = ReturnType<typeof createBashTool>;\n","const MAX_OUTPUT_CHARS = 10_000;\n\n/**\n * Truncate a string if it exceeds the max length\n */\nexport function truncateOutput(\n output: string,\n maxChars: number = MAX_OUTPUT_CHARS\n): string {\n if (output.length <= maxChars) {\n return output;\n }\n\n const halfMax = Math.floor(maxChars / 2);\n const truncatedChars = output.length - maxChars;\n\n return (\n output.slice(0, halfMax) +\n `\\n\\n... [TRUNCATED: ${truncatedChars.toLocaleString()} characters omitted] ...\\n\\n` +\n output.slice(-halfMax)\n );\n}\n\n/**\n * Calculate the total character count of messages\n */\nexport function calculateContextSize(messages: Array<{ content: unknown }>): number {\n return messages.reduce((total, msg) => {\n const content = typeof msg.content === 'string' \n ? msg.content \n : JSON.stringify(msg.content);\n return total + content.length;\n }, 0);\n}\n\n/**\n * Format bytes to human readable string\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * Format number with commas\n */\nexport function formatNumber(num: number): string {\n return num.toLocaleString();\n}\n","/**\n * tmux wrapper for terminal session management\n * \n * Provides a thin abstraction over tmux commands for:\n * - Session creation and management\n * - Output capture and logging\n * - Process lifecycle management\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { mkdir, writeFile, readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { nanoid } from 'nanoid';\n\nconst execAsync = promisify(exec);\n\n// Session prefix for all sparkecoder terminals\nconst SESSION_PREFIX = 'spark_';\n\n// Log directory base path - now session-scoped\nconst LOG_BASE_DIR = '.sparkecoder/sessions';\n\nexport interface TerminalMeta {\n id: string;\n command: string;\n cwd: string;\n createdAt: string;\n sessionId: string;\n background: boolean;\n}\n\nexport interface TerminalResult {\n id: string;\n output: string;\n exitCode: number;\n status: 'completed' | 'running' | 'stopped' | 'error';\n}\n\n// Cache tmux availability check\nlet tmuxAvailableCache: boolean | null = null;\n\n/**\n * Check if tmux is installed and available\n */\nexport async function isTmuxAvailable(): Promise<boolean> {\n if (tmuxAvailableCache !== null) {\n return tmuxAvailableCache;\n }\n \n try {\n const { stdout } = await execAsync('tmux -V');\n tmuxAvailableCache = true;\n // console.log(`[tmux] Available: ${stdout.trim()}`);\n return true;\n } catch (error) {\n tmuxAvailableCache = false;\n console.log(`[tmux] Not available: ${error instanceof Error ? error.message : 'unknown error'}`);\n return false;\n }\n}\n\n/**\n * Generate a unique terminal ID\n * Ensure it starts with a letter (tmux session names work better this way)\n */\nexport function generateTerminalId(): string {\n // Prefix with 't' to ensure it starts with a letter (nanoid can start with - or _)\n return 't' + nanoid(9);\n}\n\n/**\n * Get the tmux session name for a terminal ID\n */\nexport function getSessionName(terminalId: string): string {\n return `${SESSION_PREFIX}${terminalId}`;\n}\n\n/**\n * Get the log directory for a terminal (session-scoped)\n */\nexport function getLogDir(terminalId: string, workingDirectory: string, sessionId?: string): string {\n if (sessionId) {\n // New session-scoped path: .sparkecoder/sessions/{sessionId}/terminals/{terminalId}/\n return join(workingDirectory, LOG_BASE_DIR, sessionId, 'terminals', terminalId);\n }\n // Fallback for legacy terminals without sessionId\n return join(workingDirectory, '.sparkecoder/terminals', terminalId);\n}\n\n/**\n * Escape a string for shell command\n */\nfunction shellEscape(str: string): string {\n // Use single quotes and escape any single quotes in the string\n return `'${str.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/**\n * Create log directory and metadata file\n */\nasync function initLogDir(terminalId: string, meta: TerminalMeta, workingDirectory: string): Promise<string> {\n const logDir = getLogDir(terminalId, workingDirectory, meta.sessionId);\n await mkdir(logDir, { recursive: true });\n await writeFile(join(logDir, 'meta.json'), JSON.stringify(meta, null, 2));\n // Create empty output.log\n await writeFile(join(logDir, 'output.log'), '');\n return logDir;\n}\n\n/**\n * Poll until a condition is met or timeout\n */\nasync function pollUntil(\n condition: () => Promise<boolean>,\n options: { timeout: number; interval?: number }\n): Promise<boolean> {\n const { timeout, interval = 100 } = options;\n const startTime = Date.now();\n \n while (Date.now() - startTime < timeout) {\n if (await condition()) {\n return true;\n }\n await new Promise(r => setTimeout(r, interval));\n }\n \n return false;\n}\n\n/**\n * Run a command synchronously in tmux (waits for completion)\n */\nexport async function runSync(\n command: string,\n workingDirectory: string,\n options: { sessionId: string; timeout?: number; terminalId?: string }\n): Promise<TerminalResult> {\n if (!options) {\n throw new Error('runSync: options parameter is required (must include sessionId)');\n }\n const id = options.terminalId || generateTerminalId();\n const session = getSessionName(id);\n const logDir = await initLogDir(id, {\n id,\n command,\n cwd: workingDirectory,\n createdAt: new Date().toISOString(),\n sessionId: options.sessionId,\n background: false,\n }, workingDirectory);\n \n const logFile = join(logDir, 'output.log');\n const exitCodeFile = join(logDir, 'exit_code');\n const timeout = options.timeout || 120000; // 2 minute default\n \n try {\n // Wrap command to write exit code to a file when done\n // Also write output to the log file directly (more reliable than pipe-pane for quick commands)\n const wrappedCommand = `(${command}) 2>&1 | tee -a ${shellEscape(logFile)}; echo $? > ${shellEscape(exitCodeFile)}`;\n \n // Start tmux session\n await execAsync(\n `tmux new-session -d -s ${session} -c ${shellEscape(workingDirectory)} ${shellEscape(wrappedCommand)}`,\n { timeout: 5000 }\n );\n \n // Try to pipe output to log file (may fail if command completes quickly, that's ok)\n try {\n await execAsync(\n `tmux pipe-pane -t ${session} -o 'cat >> ${shellEscape(logFile)}'`,\n { timeout: 1000 }\n );\n } catch {\n // Session may have already ended - that's fine, we use tee in the command\n }\n \n // Poll until session ends or timeout\n const completed = await pollUntil(\n async () => {\n try {\n await execAsync(`tmux has-session -t ${session}`, { timeout: 1000 });\n return false; // Session still exists\n } catch {\n return true; // Session ended\n }\n },\n { timeout, interval: 100 }\n );\n \n if (!completed) {\n // Timeout - kill the session\n try {\n await execAsync(`tmux kill-session -t ${session}`, { timeout: 5000 });\n } catch {\n // Ignore\n }\n \n // Read whatever output we have\n let output = '';\n try {\n output = await readFile(logFile, 'utf-8');\n } catch {\n // Ignore\n }\n \n return {\n id,\n output: output.trim(),\n exitCode: 124, // Standard timeout exit code\n status: 'error',\n };\n }\n \n // Session ended - read output and exit code\n // Give a moment for log file to be flushed\n await new Promise(r => setTimeout(r, 50));\n \n let output = '';\n try {\n output = await readFile(logFile, 'utf-8');\n } catch {\n // Ignore\n }\n \n // Read exit code\n let exitCode = 0;\n try {\n if (existsSync(exitCodeFile)) {\n const exitCodeStr = await readFile(exitCodeFile, 'utf-8');\n exitCode = parseInt(exitCodeStr.trim(), 10) || 0;\n }\n } catch {\n // Ignore exit code read errors\n }\n \n return {\n id,\n output: output.trim(),\n exitCode,\n status: 'completed',\n };\n } catch (error: any) {\n // Try to kill the session on any error\n try {\n await execAsync(`tmux kill-session -t ${session}`, { timeout: 5000 });\n } catch {\n // Ignore\n }\n \n throw error;\n }\n}\n\n/**\n * Run a command in the background (returns immediately)\n */\nexport async function runBackground(\n command: string,\n workingDirectory: string,\n options: { sessionId: string; terminalId?: string }\n): Promise<TerminalResult> {\n if (!options) {\n throw new Error('runBackground: options parameter is required (must include sessionId)');\n }\n const id = options.terminalId || generateTerminalId();\n const session = getSessionName(id);\n const logDir = await initLogDir(id, {\n id,\n command,\n cwd: workingDirectory,\n createdAt: new Date().toISOString(),\n sessionId: options.sessionId,\n background: true,\n }, workingDirectory);\n \n const logFile = join(logDir, 'output.log');\n \n // Wrap command to log output via tee (more reliable than pipe-pane)\n const wrappedCommand = `(${command}) 2>&1 | tee -a ${shellEscape(logFile)}`;\n \n // Start tmux session (don't wait for completion)\n await execAsync(\n `tmux new-session -d -s ${session} -c ${shellEscape(workingDirectory)} ${shellEscape(wrappedCommand)}`,\n { timeout: 5000 }\n );\n \n return {\n id,\n output: '',\n exitCode: 0,\n status: 'running',\n };\n}\n\n/**\n * Get logs from a terminal\n */\nexport async function getLogs(\n terminalId: string,\n workingDirectory: string,\n options: { tail?: number; sessionId?: string } = {}\n): Promise<{ output: string; status: 'running' | 'stopped' | 'unknown' }> {\n const session = getSessionName(terminalId);\n const logDir = getLogDir(terminalId, workingDirectory, options.sessionId);\n const logFile = join(logDir, 'output.log');\n \n // Check if session is still running\n let isRunning = false;\n try {\n await execAsync(`tmux has-session -t ${session}`, { timeout: 5000 });\n isRunning = true;\n } catch {\n // Session not running\n }\n \n // Try to capture from tmux first (more up-to-date)\n if (isRunning) {\n try {\n const lines = options.tail || 1000;\n const { stdout } = await execAsync(\n `tmux capture-pane -t ${session} -p -S -${lines}`,\n { timeout: 5000, maxBuffer: 10 * 1024 * 1024 }\n );\n return { output: stdout.trim(), status: 'running' };\n } catch {\n // Fall through to file-based approach\n }\n }\n \n // Fall back to log file\n try {\n let output = await readFile(logFile, 'utf-8');\n \n if (options.tail) {\n const lines = output.split('\\n');\n output = lines.slice(-options.tail).join('\\n');\n }\n \n return { output: output.trim(), status: isRunning ? 'running' : 'stopped' };\n } catch {\n return { output: '', status: 'unknown' };\n }\n}\n\n/**\n * Check if a terminal is running\n */\nexport async function isRunning(terminalId: string): Promise<boolean> {\n const session = getSessionName(terminalId);\n try {\n await execAsync(`tmux has-session -t ${session}`, { timeout: 5000 });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Kill a terminal session\n */\nexport async function killTerminal(terminalId: string): Promise<boolean> {\n const session = getSessionName(terminalId);\n try {\n await execAsync(`tmux kill-session -t ${session}`, { timeout: 5000 });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * List all sparkecoder terminal sessions\n */\nexport async function listSessions(): Promise<string[]> {\n try {\n const { stdout } = await execAsync(\n `tmux list-sessions -F '#{session_name}' 2>/dev/null || true`,\n { timeout: 5000 }\n );\n \n return stdout\n .trim()\n .split('\\n')\n .filter(name => name.startsWith(SESSION_PREFIX))\n .map(name => name.slice(SESSION_PREFIX.length));\n } catch {\n return [];\n }\n}\n\n/**\n * Get metadata for a terminal\n */\nexport async function getMeta(terminalId: string, workingDirectory: string, sessionId?: string): Promise<TerminalMeta | null> {\n const logDir = getLogDir(terminalId, workingDirectory, sessionId);\n const metaFile = join(logDir, 'meta.json');\n \n try {\n const content = await readFile(metaFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * List all terminals for a session\n */\nexport async function listSessionTerminals(\n sessionId: string,\n workingDirectory: string\n): Promise<TerminalMeta[]> {\n const terminalsDir = join(workingDirectory, LOG_BASE_DIR, sessionId, 'terminals');\n const terminals: TerminalMeta[] = [];\n \n try {\n const { readdir } = await import('node:fs/promises');\n const entries = await readdir(terminalsDir, { withFileTypes: true });\n \n for (const entry of entries) {\n if (entry.isDirectory()) {\n const meta = await getMeta(entry.name, workingDirectory, sessionId);\n if (meta) {\n terminals.push(meta);\n }\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n \n return terminals;\n}\n\n/**\n * Send input (keystrokes) to a running terminal\n * Use this to respond to interactive prompts\n */\nexport async function sendInput(terminalId: string, input: string, options: { pressEnter?: boolean } = {}): Promise<boolean> {\n const session = getSessionName(terminalId);\n const { pressEnter = true } = options;\n \n try {\n // Check if session exists first\n await execAsync(`tmux has-session -t ${session}`, { timeout: 1000 });\n \n // Send the input using tmux send-keys with -l (literal) flag\n await execAsync(\n `tmux send-keys -t ${session} -l ${shellEscape(input)}`,\n { timeout: 1000 }\n );\n \n // Send Enter key separately if requested\n if (pressEnter) {\n await execAsync(\n `tmux send-keys -t ${session} Enter`,\n { timeout: 1000 }\n );\n }\n \n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Send special keys to a terminal (like arrow keys, escape, etc.)\n */\nexport async function sendKey(terminalId: string, key: 'Enter' | 'Escape' | 'Up' | 'Down' | 'Left' | 'Right' | 'Tab' | 'C-c' | 'C-d' | 'y' | 'n'): Promise<boolean> {\n const session = getSessionName(terminalId);\n \n try {\n await execAsync(`tmux has-session -t ${session}`, { timeout: 1000 });\n await execAsync(`tmux send-keys -t ${session} ${key}`, { timeout: 1000 });\n return true;\n } catch {\n return false;\n }\n}\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { readFile, stat } from 'node:fs/promises';\nimport { resolve, relative, isAbsolute } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { truncateOutput } from '../utils/truncate.js';\n\nconst MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB\nconst MAX_OUTPUT_CHARS = 50_000;\n\nexport interface ReadFileToolOptions {\n workingDirectory: string;\n}\n\nconst readFileInputSchema = z.object({\n path: z\n .string()\n .describe('The path to the file to read. Can be relative to working directory or absolute.'),\n startLine: z\n .number()\n .optional()\n .describe('Optional: Start reading from this line number (1-indexed)'),\n endLine: z\n .number()\n .optional()\n .describe('Optional: Stop reading at this line number (1-indexed, inclusive)'),\n});\n\nexport function createReadFileTool(options: ReadFileToolOptions) {\n return tool({\n description: `Read the contents of a file. Provide a path relative to the working directory (${options.workingDirectory}) or an absolute path.\nLarge files will be automatically truncated. Binary files are not supported.\nUse this to understand existing code, check file contents, or gather context.`,\n\n inputSchema: readFileInputSchema,\n\n execute: async ({ path, startLine, endLine }: z.infer<typeof readFileInputSchema>) => {\n try {\n // Resolve the path\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(options.workingDirectory, path);\n\n // Security check: ensure path is within working directory or is explicitly absolute\n const relativePath = relative(options.workingDirectory, absolutePath);\n if (relativePath.startsWith('..') && !isAbsolute(path)) {\n return {\n success: false,\n error: 'Path escapes the working directory. Use an absolute path if intentional.',\n content: null,\n };\n }\n\n // Check if file exists\n if (!existsSync(absolutePath)) {\n return {\n success: false,\n error: `File not found: ${path}`,\n content: null,\n };\n }\n\n // Check file size\n const stats = await stat(absolutePath);\n if (stats.size > MAX_FILE_SIZE) {\n return {\n success: false,\n error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,\n content: null,\n };\n }\n\n // Check if it's a directory\n if (stats.isDirectory()) {\n return {\n success: false,\n error: 'Path is a directory, not a file. Use bash with \"ls\" to list directory contents.',\n content: null,\n };\n }\n\n // Read the file\n let content = await readFile(absolutePath, 'utf-8');\n\n // Handle line range\n if (startLine !== undefined || endLine !== undefined) {\n const lines = content.split('\\n');\n const start = (startLine ?? 1) - 1;\n const end = endLine ?? lines.length;\n \n if (start < 0 || start >= lines.length) {\n return {\n success: false,\n error: `Start line ${startLine} is out of range. File has ${lines.length} lines.`,\n content: null,\n };\n }\n\n content = lines.slice(start, end).join('\\n');\n \n // Add line number context\n const lineNumbers = lines\n .slice(start, end)\n .map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`)\n .join('\\n');\n \n content = lineNumbers;\n }\n\n // Truncate if necessary\n const truncatedContent = truncateOutput(content, MAX_OUTPUT_CHARS);\n const wasTruncated = truncatedContent.length < content.length;\n\n return {\n success: true,\n path: absolutePath,\n relativePath: relative(options.workingDirectory, absolutePath),\n content: truncatedContent,\n lineCount: content.split('\\n').length,\n wasTruncated,\n sizeBytes: stats.size,\n };\n } catch (error: any) {\n // Check for binary file\n if (error.code === 'ERR_INVALID_ARG_VALUE' || error.message.includes('encoding')) {\n return {\n success: false,\n error: 'File appears to be binary and cannot be read as text.',\n content: null,\n };\n }\n\n return {\n success: false,\n error: error.message,\n content: null,\n };\n }\n },\n });\n}\n\nexport type ReadFileTool = ReturnType<typeof createReadFileTool>;\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { resolve, relative, isAbsolute, dirname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { backupFile } from '../checkpoints/index.js';\n\nexport interface WriteFileToolOptions {\n workingDirectory: string;\n sessionId: string;\n}\n\nconst writeFileInputSchema = z.object({\n path: z\n .string()\n .describe('The path to the file. Can be relative to working directory or absolute.'),\n mode: z\n .enum(['full', 'str_replace'])\n .describe('Write mode: \"full\" for complete file write, \"str_replace\" for targeted string replacement'),\n content: z\n .string()\n .optional()\n .describe('For \"full\" mode: The complete content to write to the file'),\n old_string: z\n .string()\n .optional()\n .describe('For \"str_replace\" mode: The exact string to find and replace'),\n new_string: z\n .string()\n .optional()\n .describe('For \"str_replace\" mode: The string to replace old_string with'),\n});\n\nexport function createWriteFileTool(options: WriteFileToolOptions) {\n return tool({\n description: `Write content to a file. Supports two modes:\n1. \"full\" - Write the entire file content (creates new file or replaces existing)\n2. \"str_replace\" - Replace a specific string in an existing file (for precise edits)\n\nFor str_replace mode:\n- Provide the exact string to find (old_string) and its replacement (new_string)\n- The old_string must match EXACTLY (including whitespace and indentation)\n- Only the first occurrence is replaced\n- Use this for surgical edits to existing code\n\nFor full mode:\n- Provide the complete file content\n- Creates parent directories if they don't exist\n- Use this for new files or complete rewrites\n\nWorking directory: ${options.workingDirectory}`,\n\n inputSchema: writeFileInputSchema,\n\n execute: async ({ path, mode, content, old_string, new_string }: z.infer<typeof writeFileInputSchema>) => {\n try {\n // Resolve the path\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(options.workingDirectory, path);\n\n // Security check\n const relativePath = relative(options.workingDirectory, absolutePath);\n if (relativePath.startsWith('..') && !isAbsolute(path)) {\n return {\n success: false,\n error: 'Path escapes the working directory. Use an absolute path if intentional.',\n };\n }\n\n if (mode === 'full') {\n // Full file write\n if (content === undefined) {\n return {\n success: false,\n error: 'Content is required for \"full\" mode',\n };\n }\n\n // Backup the file before modifying (for checkpoint/revert)\n await backupFile(options.sessionId, options.workingDirectory, absolutePath);\n\n // Create parent directories if needed\n const dir = dirname(absolutePath);\n if (!existsSync(dir)) {\n await mkdir(dir, { recursive: true });\n }\n\n const existed = existsSync(absolutePath);\n await writeFile(absolutePath, content, 'utf-8');\n\n return {\n success: true,\n path: absolutePath,\n relativePath: relative(options.workingDirectory, absolutePath),\n mode: 'full',\n action: existed ? 'replaced' : 'created',\n bytesWritten: Buffer.byteLength(content, 'utf-8'),\n lineCount: content.split('\\n').length,\n };\n } else if (mode === 'str_replace') {\n // String replacement mode\n if (old_string === undefined || new_string === undefined) {\n return {\n success: false,\n error: 'Both old_string and new_string are required for \"str_replace\" mode',\n };\n }\n\n if (!existsSync(absolutePath)) {\n return {\n success: false,\n error: `File not found: ${path}. Use \"full\" mode to create new files.`,\n };\n }\n\n // Backup the file before modifying (for checkpoint/revert)\n await backupFile(options.sessionId, options.workingDirectory, absolutePath);\n\n // Read current content\n const currentContent = await readFile(absolutePath, 'utf-8');\n\n // Check if old_string exists\n if (!currentContent.includes(old_string)) {\n // Provide helpful debugging info\n const lines = currentContent.split('\\n');\n const preview = lines.slice(0, 20).join('\\n');\n \n return {\n success: false,\n error: 'old_string not found in file. The string must match EXACTLY including whitespace.',\n hint: 'Check for differences in indentation, line endings, or invisible characters.',\n filePreview: lines.length > 20 \n ? `${preview}\\n... (${lines.length - 20} more lines)`\n : preview,\n };\n }\n\n // Check for multiple occurrences\n const occurrences = currentContent.split(old_string).length - 1;\n if (occurrences > 1) {\n return {\n success: false,\n error: `Found ${occurrences} occurrences of old_string. Please provide more context to make it unique.`,\n hint: 'Include surrounding lines or more specific content in old_string.',\n };\n }\n\n // Perform replacement\n const newContent = currentContent.replace(old_string, new_string);\n await writeFile(absolutePath, newContent, 'utf-8');\n\n // Calculate diff info\n const oldLines = old_string.split('\\n').length;\n const newLines = new_string.split('\\n').length;\n\n return {\n success: true,\n path: absolutePath,\n relativePath: relative(options.workingDirectory, absolutePath),\n mode: 'str_replace',\n linesRemoved: oldLines,\n linesAdded: newLines,\n lineDelta: newLines - oldLines,\n };\n }\n\n return {\n success: false,\n error: `Invalid mode: ${mode}`,\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\nexport type WriteFileTool = ReturnType<typeof createWriteFileTool>;\n","/**\n * Checkpoint system for session revert functionality\n * \n * Creates checkpoints before each user message, backs up modified files,\n * and allows reverting to any previous checkpoint.\n */\n\nimport { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { resolve, relative, dirname } from 'node:path';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport {\n checkpointQueries,\n fileBackupQueries,\n messageQueries,\n toolExecutionQueries,\n sessionQueries,\n type Checkpoint,\n type FileBackup,\n} from '../db/index.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * Get the current git HEAD commit hash (if in a git repo)\n */\nasync function getGitHead(workingDirectory: string): Promise<string | undefined> {\n try {\n const { stdout } = await execAsync('git rev-parse HEAD', {\n cwd: workingDirectory,\n timeout: 5000,\n });\n return stdout.trim();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check if a directory is a git repository\n */\nasync function isGitRepo(workingDirectory: string): Promise<boolean> {\n try {\n await execAsync('git rev-parse --git-dir', {\n cwd: workingDirectory,\n timeout: 5000,\n });\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface CheckpointManager {\n sessionId: string;\n workingDirectory: string;\n currentCheckpointId: string | null;\n}\n\n// Store for active checkpoint managers (one per session)\nconst activeManagers = new Map<string, CheckpointManager>();\n\n/**\n * Get or create a checkpoint manager for a session\n */\nexport function getCheckpointManager(sessionId: string, workingDirectory: string): CheckpointManager {\n let manager = activeManagers.get(sessionId);\n if (!manager) {\n manager = {\n sessionId,\n workingDirectory,\n currentCheckpointId: null,\n };\n activeManagers.set(sessionId, manager);\n }\n return manager;\n}\n\n/**\n * Create a new checkpoint before processing a user message\n * Called when a user message is about to be processed\n */\nexport async function createCheckpoint(\n sessionId: string,\n workingDirectory: string,\n messageSequence: number\n): Promise<Checkpoint> {\n // Get git HEAD if available\n const gitHead = await getGitHead(workingDirectory);\n\n // Create the checkpoint record\n const checkpoint = checkpointQueries.create({\n sessionId,\n messageSequence,\n gitHead,\n });\n\n // Update the manager with the current checkpoint\n const manager = getCheckpointManager(sessionId, workingDirectory);\n manager.currentCheckpointId = checkpoint.id;\n\n return checkpoint;\n}\n\n/**\n * Backup a file before it's modified\n * Called by the write_file tool before writing\n */\nexport async function backupFile(\n sessionId: string,\n workingDirectory: string,\n filePath: string\n): Promise<FileBackup | null> {\n const manager = getCheckpointManager(sessionId, workingDirectory);\n \n if (!manager.currentCheckpointId) {\n console.warn('[checkpoint] No active checkpoint, skipping file backup');\n return null;\n }\n\n // Normalize the file path to be relative\n const absolutePath = resolve(workingDirectory, filePath);\n const relativePath = relative(workingDirectory, absolutePath);\n\n // Check if we already have a backup for this file in this checkpoint\n if (fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {\n // Already backed up in this checkpoint, no need to backup again\n return null;\n }\n\n // Read the original content (if file exists)\n let originalContent: string | null = null;\n let existed = false;\n\n if (existsSync(absolutePath)) {\n try {\n originalContent = await readFile(absolutePath, 'utf-8');\n existed = true;\n } catch (error: any) {\n console.warn(`[checkpoint] Failed to read file for backup: ${error.message}`);\n }\n }\n\n // Create the backup record\n const backup = fileBackupQueries.create({\n checkpointId: manager.currentCheckpointId,\n sessionId,\n filePath: relativePath,\n originalContent,\n existed,\n });\n\n return backup;\n}\n\n/**\n * Revert a session to a specific checkpoint\n * This will:\n * 1. Restore all files to their state at that checkpoint\n * 2. Delete all messages after the checkpoint's message sequence\n * 3. Delete all tool executions after the checkpoint\n * 4. Delete all checkpoints after this one\n */\nexport async function revertToCheckpoint(\n sessionId: string,\n checkpointId: string\n): Promise<{\n success: boolean;\n filesRestored: number;\n filesDeleted: number;\n messagesDeleted: number;\n checkpointsDeleted: number;\n error?: string;\n}> {\n // Get the session to find working directory\n const session = sessionQueries.getById(sessionId);\n if (!session) {\n return {\n success: false,\n filesRestored: 0,\n filesDeleted: 0,\n messagesDeleted: 0,\n checkpointsDeleted: 0,\n error: 'Session not found',\n };\n }\n\n // Get the checkpoint\n const checkpoint = checkpointQueries.getById(checkpointId);\n if (!checkpoint || checkpoint.sessionId !== sessionId) {\n return {\n success: false,\n filesRestored: 0,\n filesDeleted: 0,\n messagesDeleted: 0,\n checkpointsDeleted: 0,\n error: 'Checkpoint not found',\n };\n }\n\n const workingDirectory = session.workingDirectory;\n\n // Get all file backups FROM this checkpoint onwards (these need to be reverted)\n // This includes backups from the target checkpoint since they represent changes made\n // AFTER the checkpoint was created (i.e., during processing of that user message)\n const backupsToRevert = fileBackupQueries.getFromSequence(sessionId, checkpoint.messageSequence);\n\n // Group backups by file path, keeping only the earliest backup for each file\n // (we want to restore to the state before ANY changes were made)\n const fileToEarliestBackup = new Map<string, FileBackup>();\n for (const backup of backupsToRevert) {\n if (!fileToEarliestBackup.has(backup.filePath)) {\n fileToEarliestBackup.set(backup.filePath, backup);\n }\n }\n\n let filesRestored = 0;\n let filesDeleted = 0;\n\n // Restore each file\n for (const [filePath, backup] of fileToEarliestBackup) {\n const absolutePath = resolve(workingDirectory, filePath);\n\n try {\n if (backup.existed && backup.originalContent !== null) {\n // File existed before - restore its content\n const dir = dirname(absolutePath);\n if (!existsSync(dir)) {\n await mkdir(dir, { recursive: true });\n }\n await writeFile(absolutePath, backup.originalContent, 'utf-8');\n filesRestored++;\n } else if (!backup.existed) {\n // File didn't exist before - delete it\n if (existsSync(absolutePath)) {\n await unlink(absolutePath);\n filesDeleted++;\n }\n }\n } catch (error: any) {\n console.error(`Failed to restore ${filePath}: ${error.message}`);\n }\n }\n\n // Delete messages from the checkpoint's message sequence onwards\n const messagesDeleted = messageQueries.deleteFromSequence(sessionId, checkpoint.messageSequence);\n\n // Delete tool executions after the checkpoint was created\n toolExecutionQueries.deleteAfterTime(sessionId, checkpoint.createdAt);\n\n // Delete checkpoints after this one (the file backups are deleted via CASCADE)\n const checkpointsDeleted = checkpointQueries.deleteAfterSequence(sessionId, checkpoint.messageSequence);\n\n // Update the manager\n const manager = getCheckpointManager(sessionId, workingDirectory);\n manager.currentCheckpointId = checkpoint.id;\n\n return {\n success: true,\n filesRestored,\n filesDeleted,\n messagesDeleted,\n checkpointsDeleted,\n };\n}\n\n/**\n * Get all checkpoints for a session\n */\nexport function getCheckpoints(sessionId: string): Checkpoint[] {\n return checkpointQueries.getBySession(sessionId);\n}\n\n/**\n * Get the diff for an entire session (all file changes from start to now)\n */\nexport async function getSessionDiff(\n sessionId: string\n): Promise<{\n files: Array<{\n path: string;\n status: 'created' | 'modified' | 'deleted';\n originalContent: string | null;\n currentContent: string | null;\n }>;\n}> {\n const session = sessionQueries.getById(sessionId);\n if (!session) {\n return { files: [] };\n }\n\n const workingDirectory = session.workingDirectory;\n\n // Get all file backups for this session\n const allBackups = fileBackupQueries.getBySession(sessionId);\n\n // Group by file path, keeping the earliest backup (original state)\n const fileToOriginalBackup = new Map<string, FileBackup>();\n for (const backup of allBackups) {\n if (!fileToOriginalBackup.has(backup.filePath)) {\n fileToOriginalBackup.set(backup.filePath, backup);\n }\n }\n\n const files: Array<{\n path: string;\n status: 'created' | 'modified' | 'deleted';\n originalContent: string | null;\n currentContent: string | null;\n }> = [];\n\n for (const [filePath, originalBackup] of fileToOriginalBackup) {\n const absolutePath = resolve(workingDirectory, filePath);\n \n // Get current content\n let currentContent: string | null = null;\n let currentExists = false;\n \n if (existsSync(absolutePath)) {\n try {\n currentContent = await readFile(absolutePath, 'utf-8');\n currentExists = true;\n } catch {\n // File exists but can't be read\n }\n }\n\n // Determine status\n let status: 'created' | 'modified' | 'deleted';\n if (!originalBackup.existed && currentExists) {\n status = 'created';\n } else if (originalBackup.existed && !currentExists) {\n status = 'deleted';\n } else {\n status = 'modified';\n }\n\n files.push({\n path: filePath,\n status,\n originalContent: originalBackup.originalContent,\n currentContent,\n });\n }\n\n return { files };\n}\n\n/**\n * Clear the checkpoint manager for a session (called when session is deleted)\n */\nexport function clearCheckpointManager(sessionId: string): void {\n activeManagers.delete(sessionId);\n}\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { todoQueries, TodoItem } from '../db/index.js';\n\nexport interface TodoToolOptions {\n sessionId: string;\n}\n\nconst todoInputSchema = z.object({\n action: z\n .enum(['add', 'list', 'mark', 'clear'])\n .describe('The action to perform on the todo list'),\n items: z\n .array(\n z.object({\n content: z.string().describe('Description of the task'),\n order: z.number().optional().describe('Optional order/priority (lower = higher priority)'),\n })\n )\n .optional()\n .describe('For \"add\" action: Array of todo items to add'),\n todoId: z\n .string()\n .optional()\n .describe('For \"mark\" action: The ID of the todo item to update'),\n status: z\n .enum(['pending', 'in_progress', 'completed', 'cancelled'])\n .optional()\n .describe('For \"mark\" action: The new status for the todo item'),\n});\n\nexport function createTodoTool(options: TodoToolOptions) {\n return tool({\n description: `Manage your task list for the current session. Use this to:\n- Break down complex tasks into smaller steps\n- Track progress on multi-step operations\n- Organize your work systematically\n\nAvailable actions:\n- \"add\": Add one or more new todo items to the list\n- \"list\": View all current todo items and their status\n- \"mark\": Update the status of a todo item (pending, in_progress, completed, cancelled)\n- \"clear\": Remove all todo items from the list\n\nBest practices:\n- Add todos before starting complex tasks\n- Mark items as \"in_progress\" when actively working on them\n- Update status as you complete each step`,\n\n inputSchema: todoInputSchema,\n\n execute: async ({ action, items, todoId, status }: z.infer<typeof todoInputSchema>) => {\n try {\n switch (action) {\n case 'add': {\n if (!items || items.length === 0) {\n return {\n success: false,\n error: 'No items provided. Include at least one todo item.',\n };\n }\n\n const created = todoQueries.createMany(options.sessionId, items);\n \n return {\n success: true,\n action: 'add',\n itemsAdded: created.length,\n items: created.map(formatTodoItem),\n };\n }\n\n case 'list': {\n const todos = todoQueries.getBySession(options.sessionId);\n \n const stats = {\n total: todos.length,\n pending: todos.filter((t) => t.status === 'pending').length,\n inProgress: todos.filter((t) => t.status === 'in_progress').length,\n completed: todos.filter((t) => t.status === 'completed').length,\n cancelled: todos.filter((t) => t.status === 'cancelled').length,\n };\n\n return {\n success: true,\n action: 'list',\n stats,\n items: todos.map(formatTodoItem),\n };\n }\n\n case 'mark': {\n if (!todoId) {\n return {\n success: false,\n error: 'todoId is required for \"mark\" action',\n };\n }\n\n if (!status) {\n return {\n success: false,\n error: 'status is required for \"mark\" action',\n };\n }\n\n const updated = todoQueries.updateStatus(todoId, status);\n \n if (!updated) {\n return {\n success: false,\n error: `Todo item not found: ${todoId}`,\n };\n }\n\n return {\n success: true,\n action: 'mark',\n item: formatTodoItem(updated),\n };\n }\n\n case 'clear': {\n const count = todoQueries.clearSession(options.sessionId);\n \n return {\n success: true,\n action: 'clear',\n itemsRemoved: count,\n };\n }\n\n default:\n return {\n success: false,\n error: `Unknown action: ${action}`,\n };\n }\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\nfunction formatTodoItem(item: TodoItem) {\n return {\n id: item.id,\n content: item.content,\n status: item.status,\n order: item.order,\n createdAt: item.createdAt.toISOString(),\n };\n}\n\nexport type TodoTool = ReturnType<typeof createTodoTool>;\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { loadAllSkills, loadSkillContent, formatSkillsForContext } from '../skills/index.js';\nimport { skillQueries } from '../db/index.js';\n\nexport interface LoadSkillToolOptions {\n sessionId: string;\n skillsDirectories: string[];\n}\n\nconst loadSkillInputSchema = z.object({\n action: z\n .enum(['list', 'load'])\n .describe('Action to perform: \"list\" to see available skills, \"load\" to load a skill'),\n skillName: z\n .string()\n .optional()\n .describe('For \"load\" action: The name of the skill to load'),\n});\n\nexport function createLoadSkillTool(options: LoadSkillToolOptions) {\n return tool({\n description: `Load a skill document into the conversation context. Skills are specialized knowledge files that provide guidance on specific topics like debugging, code review, architecture patterns, etc.\n\nAvailable actions:\n- \"list\": Show all available skills with their descriptions\n- \"load\": Load a specific skill's full content into context\n\nUse this when you need specialized knowledge or guidance for a particular task.\nOnce loaded, a skill's content will be available in the conversation context.`,\n\n inputSchema: loadSkillInputSchema,\n\n execute: async ({ action, skillName }: z.infer<typeof loadSkillInputSchema>) => {\n try {\n switch (action) {\n case 'list': {\n const skills = await loadAllSkills(options.skillsDirectories);\n \n return {\n success: true,\n action: 'list',\n skillCount: skills.length,\n skills: skills.map((s) => ({\n name: s.name,\n description: s.description,\n })),\n formatted: formatSkillsForContext(skills),\n };\n }\n\n case 'load': {\n if (!skillName) {\n return {\n success: false,\n error: 'skillName is required for \"load\" action',\n };\n }\n\n // Check if already loaded\n if (skillQueries.isLoaded(options.sessionId, skillName)) {\n return {\n success: false,\n error: `Skill \"${skillName}\" is already loaded in this session`,\n };\n }\n\n // Load the skill content\n const skill = await loadSkillContent(skillName, options.skillsDirectories);\n \n if (!skill) {\n const allSkills = await loadAllSkills(options.skillsDirectories);\n return {\n success: false,\n error: `Skill \"${skillName}\" not found`,\n availableSkills: allSkills.map((s) => s.name),\n };\n }\n\n // Record that we loaded this skill\n skillQueries.load(options.sessionId, skillName);\n\n return {\n success: true,\n action: 'load',\n skillName: skill.name,\n description: skill.description,\n content: skill.content,\n contentLength: skill.content.length,\n };\n }\n\n default:\n return {\n success: false,\n error: `Unknown action: ${action}`,\n };\n }\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\nexport type LoadSkillTool = ReturnType<typeof createLoadSkillTool>;\n","import { readFile, readdir } from 'node:fs/promises';\nimport { resolve, basename, extname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { SkillMetadata, SkillMetadataSchema } from '../config/types.js';\n\nexport interface Skill {\n name: string;\n description: string;\n filePath: string;\n content?: string; // Only loaded when explicitly requested\n}\n\nexport interface SkillWithContent extends Skill {\n content: string;\n}\n\n/**\n * Parse skill metadata from frontmatter\n */\nfunction parseSkillFrontmatter(content: string): { metadata: SkillMetadata; body: string } | null {\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n \n if (!frontmatterMatch) {\n return null;\n }\n\n const [, frontmatter, body] = frontmatterMatch;\n \n try {\n // Simple YAML-like parsing for name and description\n const lines = frontmatter.split('\\n');\n const data: Record<string, string> = {};\n \n for (const line of lines) {\n const colonIndex = line.indexOf(':');\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim();\n let value = line.slice(colonIndex + 1).trim();\n // Remove quotes if present\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n data[key] = value;\n }\n }\n\n const metadata = SkillMetadataSchema.parse(data);\n return { metadata, body: body.trim() };\n } catch {\n return null;\n }\n}\n\n/**\n * Get skill name from filename if no frontmatter\n */\nfunction getSkillNameFromPath(filePath: string): string {\n return basename(filePath, extname(filePath))\n .replace(/[-_]/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Load all skills from a directory (metadata only)\n */\nexport async function loadSkillsFromDirectory(directory: string): Promise<Skill[]> {\n if (!existsSync(directory)) {\n return [];\n }\n\n const skills: Skill[] = [];\n const files = await readdir(directory);\n\n for (const file of files) {\n if (!file.endsWith('.md')) continue;\n\n const filePath = resolve(directory, file);\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n\n if (parsed) {\n skills.push({\n name: parsed.metadata.name,\n description: parsed.metadata.description,\n filePath,\n });\n } else {\n // Use filename as name, first paragraph as description\n const name = getSkillNameFromPath(filePath);\n const firstParagraph = content.split('\\n\\n')[0]?.slice(0, 200) || 'No description';\n \n skills.push({\n name,\n description: firstParagraph.replace(/^#\\s*/, '').trim(),\n filePath,\n });\n }\n }\n\n return skills;\n}\n\n/**\n * Load all skills from multiple directories\n */\nexport async function loadAllSkills(directories: string[]): Promise<Skill[]> {\n const allSkills: Skill[] = [];\n const seenNames = new Set<string>();\n\n for (const dir of directories) {\n const skills = await loadSkillsFromDirectory(dir);\n for (const skill of skills) {\n // Avoid duplicates (first one wins)\n if (!seenNames.has(skill.name.toLowerCase())) {\n seenNames.add(skill.name.toLowerCase());\n allSkills.push(skill);\n }\n }\n }\n\n return allSkills;\n}\n\n/**\n * Load a skill's full content by name\n */\nexport async function loadSkillContent(\n skillName: string,\n directories: string[]\n): Promise<SkillWithContent | null> {\n const allSkills = await loadAllSkills(directories);\n const skill = allSkills.find(\n (s) => s.name.toLowerCase() === skillName.toLowerCase()\n );\n\n if (!skill) {\n return null;\n }\n\n const content = await readFile(skill.filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n\n return {\n ...skill,\n content: parsed ? parsed.body : content,\n };\n}\n\n/**\n * Format skills list for context\n */\nexport function formatSkillsForContext(skills: Skill[]): string {\n if (skills.length === 0) {\n return 'No skills available.';\n }\n\n const lines = ['Available skills (use load_skill tool to load into context):'];\n for (const skill of skills) {\n lines.push(`- ${skill.name}: ${skill.description}`);\n }\n\n return lines.join('\\n');\n}\n","import { ToolSet } from 'ai';\nimport { createBashTool, BashToolOptions, BashToolProgress } from './bash.js';\nimport { createReadFileTool, ReadFileToolOptions } from './read-file.js';\nimport { createWriteFileTool, WriteFileToolOptions } from './write-file.js';\nimport { createTodoTool, TodoToolOptions } from './todo.js';\nimport { createLoadSkillTool, LoadSkillToolOptions } from './load-skill.js';\n\nexport interface CreateToolsOptions {\n sessionId: string;\n workingDirectory: string;\n skillsDirectories: string[];\n onBashOutput?: (output: string) => void;\n onBashProgress?: (progress: BashToolProgress) => void;\n}\n\n/**\n * Create all tools for an agent session\n */\nexport function createTools(options: CreateToolsOptions): ToolSet {\n return {\n bash: createBashTool({\n workingDirectory: options.workingDirectory,\n sessionId: options.sessionId,\n onOutput: options.onBashOutput,\n onProgress: options.onBashProgress,\n }),\n\n read_file: createReadFileTool({\n workingDirectory: options.workingDirectory,\n }),\n\n write_file: createWriteFileTool({\n workingDirectory: options.workingDirectory,\n sessionId: options.sessionId,\n }),\n\n todo: createTodoTool({\n sessionId: options.sessionId,\n }),\n\n load_skill: createLoadSkillTool({\n sessionId: options.sessionId,\n skillsDirectories: options.skillsDirectories,\n }),\n };\n}\n\n// Re-export individual tool creators for customization\nexport { createBashTool } from './bash.js';\nexport { createReadFileTool } from './read-file.js';\nexport { createWriteFileTool } from './write-file.js';\nexport { createTodoTool } from './todo.js';\nexport { createLoadSkillTool } from './load-skill.js';\n\n// Export types\nexport type { BashToolOptions, BashToolProgress } from './bash.js';\nexport type { ReadFileToolOptions } from './read-file.js';\nexport type { WriteFileToolOptions } from './write-file.js';\nexport type { TodoToolOptions } from './todo.js';\nexport type { LoadSkillToolOptions } from './load-skill.js';","import { generateText, type ModelMessage as AIModelMessage } from 'ai';\nimport { gateway } from '@ai-sdk/gateway';\nimport { messageQueries, ModelMessage } from '../db/index.js';\nimport { calculateContextSize } from '../utils/truncate.js';\nimport { createSummaryPrompt } from './prompts.js';\nimport { getConfig } from '../config/index.js';\n\nexport interface ContextManagerOptions {\n sessionId: string;\n maxContextChars: number;\n keepRecentMessages: number;\n autoSummarize: boolean;\n}\n\n/**\n * Manages conversation context including history and summarization\n * \n * Uses AI SDK's ModelMessage format directly for accurate message passing.\n * Messages are stored in the exact format returned by response.messages.\n */\nexport class ContextManager {\n private sessionId: string;\n private maxContextChars: number;\n private keepRecentMessages: number;\n private autoSummarize: boolean;\n private summary: string | null = null;\n\n constructor(options: ContextManagerOptions) {\n this.sessionId = options.sessionId;\n this.maxContextChars = options.maxContextChars;\n this.keepRecentMessages = options.keepRecentMessages;\n this.autoSummarize = options.autoSummarize;\n }\n\n /**\n * Get messages for the current context\n * Returns ModelMessage[] that can be passed directly to streamText/generateText\n */\n async getMessages(): Promise<AIModelMessage[]> {\n let modelMessages = messageQueries.getModelMessages(this.sessionId) as AIModelMessage[];\n\n // Calculate context size\n const contextSize = calculateContextSize(modelMessages);\n\n // Check if we need to summarize\n if (this.autoSummarize && contextSize > this.maxContextChars) {\n modelMessages = await this.summarizeContext(modelMessages);\n }\n\n // Prepend summary if exists\n if (this.summary) {\n modelMessages = [\n {\n role: 'system' as const,\n content: `[Previous conversation summary]\\n${this.summary}`,\n },\n ...modelMessages,\n ];\n }\n\n return modelMessages;\n }\n\n /**\n * Summarize older messages to reduce context size\n */\n private async summarizeContext(messages: AIModelMessage[]): Promise<AIModelMessage[]> {\n if (messages.length <= this.keepRecentMessages) {\n return messages;\n }\n\n // Split into old and recent messages\n const splitIndex = messages.length - this.keepRecentMessages;\n const oldMessages = messages.slice(0, splitIndex);\n const recentMessages = messages.slice(splitIndex);\n\n // Format old messages for summarization\n const historyText = oldMessages\n .map((msg) => {\n const content = typeof msg.content === 'string' \n ? msg.content \n : JSON.stringify(msg.content);\n return `[${msg.role}]: ${content}`;\n })\n .join('\\n\\n');\n\n // Generate summary\n try {\n const config = getConfig();\n const summaryPrompt = createSummaryPrompt(historyText);\n\n const result = await generateText({\n model: gateway(config.defaultModel) as any,\n prompt: summaryPrompt,\n });\n\n this.summary = result.text;\n \n console.log(`[Context] Summarized ${oldMessages.length} messages into ${this.summary.length} chars`);\n\n return recentMessages;\n } catch (error) {\n console.error('[Context] Failed to summarize:', error);\n // Fall back to truncating old messages\n return recentMessages;\n }\n }\n\n /**\n * Add a user message to the context\n */\n addUserMessage(text: string): void {\n const userMessage: ModelMessage = {\n role: 'user',\n content: text,\n };\n messageQueries.create(this.sessionId, userMessage);\n }\n\n /**\n * Add response messages from AI SDK directly\n * This is the preferred method - use result.response.messages from streamText/generateText\n */\n addResponseMessages(messages: AIModelMessage[]): void {\n messageQueries.addMany(this.sessionId, messages as ModelMessage[]);\n }\n\n /**\n * Get current context statistics\n */\n getStats(): { messageCount: number; contextChars: number; hasSummary: boolean } {\n const messages = messageQueries.getModelMessages(this.sessionId) as AIModelMessage[];\n \n return {\n messageCount: messages.length,\n contextChars: calculateContextSize(messages),\n hasSummary: this.summary !== null,\n };\n }\n\n /**\n * Clear all messages in the context\n */\n clear(): void {\n messageQueries.deleteBySession(this.sessionId);\n this.summary = null;\n }\n}\n","import os from 'node:os';\nimport { loadAllSkills, formatSkillsForContext } from '../skills/index.js';\nimport { todoQueries, TodoItem } from '../db/index.js';\n\n/**\n * Get platform-specific search instructions\n */\nfunction getSearchInstructions(): string {\n const platform = process.platform;\n \n const common = `- **Prefer \\`read_file\\` over shell commands** for reading files - don't use \\`cat\\`, \\`head\\`, or \\`tail\\` when \\`read_file\\` is available\n- **Avoid unbounded searches** - always scope searches with glob patterns and directory paths to prevent overwhelming output\n- **Search strategically**: Start with specific patterns and directories, then broaden only if needed`;\n\n if (platform === 'win32') {\n return `${common}\n- **Find files**: \\`dir /s /b *.ts\\` or PowerShell: \\`Get-ChildItem -Recurse -Filter *.ts\\`\n- **Search content**: \\`findstr /s /n \"pattern\" *.ts\\` or PowerShell: \\`Select-String -Pattern \"pattern\" -Path *.ts -Recurse\\`\n- **If ripgrep (\\`rg\\`) is installed**: \\`rg \"pattern\" -t ts src/\\` - faster and respects .gitignore`;\n }\n \n // Unix-like (darwin, linux, etc.)\n return `${common}\n- **Find files**: \\`find . -name \"*.ts\"\\` or \\`find src/ -type f -name \"*.tsx\"\\`\n- **Search content**: \\`grep -rn \"pattern\" --include=\"*.ts\" src/\\` - use \\`-l\\` for filenames only, \\`-c\\` for counts\n- **If ripgrep (\\`rg\\`) is installed**: \\`rg \"pattern\" -t ts src/\\` - faster and respects .gitignore`;\n}\n\n/**\n * Build the system prompt for the coding agent\n */\nexport async function buildSystemPrompt(options: {\n workingDirectory: string;\n skillsDirectories: string[];\n sessionId: string;\n customInstructions?: string;\n}): Promise<string> {\n const { workingDirectory, skillsDirectories, sessionId, customInstructions } = options;\n\n // Load available skills\n const skills = await loadAllSkills(skillsDirectories);\n const skillsContext = formatSkillsForContext(skills);\n\n // Load current todos\n const todos = todoQueries.getBySession(sessionId);\n const todosContext = formatTodosForContext(todos);\n\n // Get environment info\n const platform = process.platform === 'win32' ? 'Windows' : process.platform === 'darwin' ? 'macOS' : 'Linux';\n const currentDate = new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n const searchInstructions = getSearchInstructions();\n\n const systemPrompt = `You are SparkECoder, an expert AI coding assistant. You help developers write, debug, and improve code.\n\n## Environment\n- **Platform**: ${platform} (${os.release()})\n- **Date**: ${currentDate}\n- **Working Directory**: ${workingDirectory}\n\n## Core Capabilities\nYou have access to powerful tools for:\n- **bash**: Execute commands in the terminal (see below for details)\n- **read_file**: Read file contents to understand code and context\n- **write_file**: Create new files or edit existing ones (supports targeted string replacement)\n- **todo**: Manage your task list to track progress on complex operations\n- **load_skill**: Load specialized knowledge documents for specific tasks\n\n\nIMPORTANT: If you have zero context of where you are working, always explore it first to understand the structure before doing things for the user.\n\nUse the TODO tool to manage your task list to track progress on complex operations. Always ask the user what they want to do specifically before doing it, and make a plan. \nStep 1 of the plan should be researching files and understanding the components/structure of what you're working on (if you don't already have context), then after u have done that, plan out the rest of the tasks u need to do. \nYou can clear the todo and restart it, and do multiple things inside of one session.\n\n### bash Tool\nThe bash tool runs commands in the terminal. Every command runs in its own session with logs saved to disk.\n\n**Run a command (default - waits for completion):**\n\\`\\`\\`\nbash({ command: \"npm install\" })\nbash({ command: \"git status\" })\n\\`\\`\\`\n\n**Run in background (for dev servers, watchers):**\n\\`\\`\\`\nbash({ command: \"npm run dev\", background: true })\n→ Returns { id: \"abc123\" } - save this ID to check logs or stop it later\n\\`\\`\\`\n\n**Check on a background process:**\n\\`\\`\\`\nbash({ id: \"abc123\" }) // get full output\nbash({ id: \"abc123\", tail: 50 }) // last 50 lines only\n\\`\\`\\`\n\n**Stop a background process:**\n\\`\\`\\`\nbash({ id: \"abc123\", kill: true })\n\\`\\`\\`\n\n**Respond to interactive prompts (for yes/no questions, etc.):**\n\\`\\`\\`\nbash({ id: \"abc123\", key: \"y\" }) // send 'y' for yes\nbash({ id: \"abc123\", key: \"n\" }) // send 'n' for no \nbash({ id: \"abc123\", key: \"Enter\" }) // press Enter\nbash({ id: \"abc123\", input: \"my text\" }) // send text input\n\\`\\`\\`\n\n**IMPORTANT - Handling Interactive Commands:**\n- ALWAYS prefer non-interactive flags when available:\n - \\`npm init --yes\\` or \\`npm install --yes\\`\n - \\`npx create-next-app --yes\\` (accepts all defaults)\n - \\`npx create-react-app --yes\\`\n - \\`git commit --no-edit\\`\n - \\`apt-get install -y\\`\n- If a command might prompt for input, run it in background mode first\n- Check the output to see if it's waiting for input\n- Use \\`key: \"y\"\\` or \\`key: \"n\"\\` for yes/no prompts\n- Use \\`input: \"text\"\\` for text input prompts\n\nLogs are saved to \\`.sparkecoder/terminals/{id}/output.log\\` and can be read with \\`read_file\\` if needed.\n\n## Guidelines\n\n### Code Quality\n- Write clean, maintainable, well-documented code\n- Follow existing code style and conventions in the project\n- Use meaningful variable and function names\n- Add comments for complex logic\n\n### Problem Solving\n- Before making changes, understand the existing code structure\n- Break complex tasks into smaller, manageable steps using the todo tool\n- Test changes when possible using the bash tool\n- Handle errors gracefully and provide helpful error messages\n\n### File Operations\n- Use \\`read_file\\` to understand code before modifying\n- Use \\`write_file\\` with mode \"str_replace\" for targeted edits to existing files\n- Use \\`write_file\\` with mode \"full\" only for new files or complete rewrites\n- Always verify changes by reading files after modifications\n\n### Searching and Exploration\n${searchInstructions}\n\nFollow these principles when designing and implementing software:\n\n1. **Modularity** — Write simple parts connected by clean interfaces\n2. **Clarity** — Clarity is better than cleverness\n3. **Composition** — Design programs to be connected to other programs\n4. **Separation** — Separate policy from mechanism; separate interfaces from engines\n5. **Simplicity** — Design for simplicity; add complexity only where you must\n6. **Parsimony** — Write a big program only when it is clear by demonstration that nothing else will do\n7. **Transparency** — Design for visibility to make inspection and debugging easier\n8. **Robustness** — Robustness is the child of transparency and simplicity\n9. **Representation** — Fold knowledge into data so program logic can be stupid and robust\n10. **Least Surprise** — In interface design, always do the least surprising thing\n11. **Silence** — When a program has nothing surprising to say, it should say nothing\n12. **Repair** — When you must fail, fail noisily and as soon as possible\n13. **Economy** — Programmer time is expensive; conserve it in preference to machine time\n14. **Generation** — Avoid hand-hacking; write programs to write programs when you can\n15. **Optimization** — Prototype before polishing. Get it working before you optimize it\n16. **Diversity** — Distrust all claims for \"one true way\"\n17. **Extensibility** — Design for the future, because it will be here sooner than you think\n\n\n### Communication\n- Explain your reasoning and approach\n- Be concise but thorough\n- Ask clarifying questions when requirements are ambiguous\n- Report progress on multi-step tasks\n\n## Skills\n${skillsContext}\n\n## Current Task List\n${todosContext}\n\n${customInstructions ? `## Custom Instructions\\n${customInstructions}` : ''}\n\nRemember: You are a helpful, capable coding assistant. Take initiative, be thorough, and deliver high-quality results.`;\n\n return systemPrompt;\n}\n\n/**\n * Format todos for system prompt context\n */\nfunction formatTodosForContext(todos: TodoItem[]): string {\n if (todos.length === 0) {\n return 'No active tasks. Use the todo tool to create a plan for complex operations.';\n }\n\n const statusEmoji: Record<string, string> = {\n pending: '⬜',\n in_progress: '🔄',\n completed: '✅',\n cancelled: '❌',\n };\n\n const lines = ['Current tasks:'];\n for (const todo of todos) {\n const emoji = statusEmoji[todo.status] || '•';\n lines.push(`${emoji} [${todo.id}] ${todo.content}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Create a summary prompt for context compression\n */\nexport function createSummaryPrompt(conversationHistory: string): string {\n return `Please provide a concise summary of the following conversation history. Focus on:\n1. The main task or goal being worked on\n2. Key decisions made\n3. Important code changes or file operations performed\n4. Current state and any pending actions\n\nKeep the summary under 2000 characters while preserving essential context for continuing the work.\n\nConversation to summarize:\n${conversationHistory}\n\nSummary:`;\n}\n"],"mappings":"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","names":["generateText","tool","gateway","z","nanoid","messages","z","exec","promisify","messages","existsSync","join","nanoid","output","execAsync","promisify","exec","MAX_OUTPUT_CHARS","z","output","status","truncatedOutput","tool","z","readFile","resolve","existsSync","MAX_OUTPUT_CHARS","z","tool","resolve","existsSync","readFile","tool","z","readFile","writeFile","mkdir","resolve","relative","isAbsolute","dirname","existsSync","readFile","writeFile","mkdir","existsSync","resolve","relative","dirname","exec","promisify","execAsync","promisify","exec","resolve","relative","existsSync","readFile","z","tool","isAbsolute","resolve","relative","dirname","existsSync","mkdir","writeFile","readFile","tool","z","z","tool","tool","z","readFile","resolve","existsSync","existsSync","resolve","readFile","z","tool","platform","messages","text","messages","gateway","generateText","tool","z","nanoid","resolve"]}