sparkecoder 0.1.46 → 0.1.48
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.
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__a87e1c27._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/{static/chunks/7e48d04fbd6dd7a6.js → chunks/826a34109ed05598.js} +1 -1
- package/web/.next/{static/chunks/7e48d04fbd6dd7a6.js → standalone/web/.next/static/static/chunks/826a34109ed05598.js} +1 -1
- package/web/.next/standalone/web/src/components/chat-interface.tsx +21 -8
- package/web/.next/{standalone/web/.next/static/chunks/7e48d04fbd6dd7a6.js → static/chunks/826a34109ed05598.js} +1 -1
- /package/web/.next/standalone/web/.next/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_ssgManifest.js +0 -0
- /package/web/.next/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_buildManifest.js +0 -0
- /package/web/.next/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{_0-MmiObGsO5iLpf0Qgmw → 9l0nvP_hXh_bNsaosB1Ho}/_ssgManifest.js +0 -0
package/dist/agent/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/types.ts","../../src/skills/index.ts","../../src/agent/index.ts","../../src/agent/model.ts","../../src/db/remote.ts","../../src/db/index.ts","../../src/config/index.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/lsp/index.ts","../../src/lsp/servers.ts","../../src/lsp/client.ts","../../src/lsp/types.ts","../../src/tools/todo.ts","../../src/tools/load-skill.ts","../../src/tools/linter.ts","../../src/tools/search.ts","../../src/agent/subagent.ts","../../src/agent/subagents/search.ts","../../src/tools/semantic-search.ts","../../src/semantic/namespace.ts","../../src/semantic/hasher.ts","../../src/semantic/chunker.ts","../../src/semantic/client.ts","../../src/semantic/indexer.ts","../../src/tools/index.ts","../../src/agent/context.ts","../../src/agent/prompts.ts","../../src/utils/sanitize-messages.ts"],"sourcesContent":["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 // Whether to always inject this skill into context (vs on-demand loading)\n alwaysApply: z.boolean().optional().default(false),\n // Glob patterns - auto-inject when working with matching files\n globs: z.array(z.string()).optional().default([]),\n});\n\n// Skill loading type\nexport type SkillLoadType = 'always' | 'on_demand' | 'glob_matched';\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// Vector Gateway configuration for semantic search\nexport const VectorGatewayConfigSchema = z\n .object({\n // Redis URL for Vector Gateway (or use VECTOR_REDIS_URL env var)\n redisUrl: z.string().optional(),\n // HTTP URL for database operations (or use VECTOR_HTTP_URL env var)\n httpUrl: z.string().optional(),\n // Embedding model to use (default: text-embedding-3-small)\n embeddingModel: z.string().default('gemini-embedding-001'),\n // Custom namespace override (auto-generated from git remote if not set)\n namespace: z.string().optional(),\n // File patterns to include in indexing\n include: z\n .array(z.string())\n .optional()\n .default([\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.py',\n '**/*.go',\n '**/*.rs',\n '**/*.java',\n '**/*.md',\n '**/*.mdx',\n '**/*.txt',\n ]),\n // File patterns to exclude from indexing\n exclude: z\n .array(z.string())\n .optional()\n .default([\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/.git/**',\n '**/.next/**',\n '**/*.min.js',\n '**/*.bundle.js',\n '**/pnpm-lock.yaml',\n '**/package-lock.json',\n '**/yarn.lock',\n '**/.test-workspace/**',\n '**/.semantic-test-workspace/**',\n '**/.semantic-integration-test/**',\n ]),\n })\n .optional();\n\n// Remote server configuration\nexport const RemoteServerConfigSchema = z\n .object({\n // URL of the remote server (e.g., https://agent.sparkecode.com)\n url: z.string().url().optional(),\n // Auth key for the remote server (auto-generated on first use if not set)\n // Can also be set via SPARKECODER_AUTH_KEY env var\n authKey: z.string().optional(),\n })\n .optional();\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-6'),\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 // Public URL for web UI to connect to API (for Docker/remote access)\n // If not set, defaults to http://{host}:{port}\n publicUrl: z.string().url().optional(),\n })\n .default({ port: 3141, host: '127.0.0.1' }),\n\n // Database path (used for local SQLite - ignored if remoteServer is configured)\n databasePath: z.string().optional().default('./sparkecoder.db'),\n\n // Remote server configuration (for centralized storage)\n // If configured, uses remote MongoDB instead of local SQLite\n remoteServer: RemoteServerConfigSchema,\n\n // Vector Gateway configuration for semantic search\n vectorGateway: VectorGatewayConfigSchema,\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 VectorGatewayConfig = z.infer<typeof VectorGatewayConfigSchema>;\nexport type RemoteServerConfig = z.infer<typeof RemoteServerConfigSchema>;\nexport type SparkcoderConfig = z.infer<typeof SparkcoderConfigSchema>;\n\n// Discovered skill sources\nexport interface DiscoveredSkills {\n // Directories where all skills are always loaded\n alwaysLoadedDirs: Array<{ path: string; priority: number }>;\n // Directories where skills are on-demand (frontmatter can override)\n onDemandDirs: Array<{ path: string; priority: number }>;\n // Path to AGENTS.md if it exists (always loaded)\n agentsMdPath: string | null;\n // All directories in priority order (for deduplication)\n allDirectories: string[];\n}\n\n// Resolved vector gateway config with env var overrides applied\nexport interface ResolvedVectorGatewayConfig {\n redisUrl: string | null;\n httpUrl: string | null;\n embeddingModel: string;\n namespace: string | null;\n include: string[];\n exclude: string[];\n}\n\n// Resolved remote server config\nexport interface ResolvedRemoteServerConfig {\n url: string | null;\n authKey: string | null;\n isConfigured: boolean;\n}\n\n// Runtime config with resolved paths\nexport interface ResolvedConfig extends Omit<SparkcoderConfig, 'server'> {\n server: {\n port: number;\n host: string;\n publicUrl?: string;\n };\n resolvedWorkingDirectory: string;\n resolvedSkillsDirectories: string[];\n resolvedDatabasePath: string;\n // Enhanced skill discovery\n discoveredSkills: DiscoveredSkills;\n // Resolved vector gateway config (with env var overrides)\n resolvedVectorGateway: ResolvedVectorGatewayConfig;\n // Resolved remote server config (with env var overrides)\n resolvedRemoteServer: ResolvedRemoteServerConfig;\n}\n","import { readFile, readdir } from 'node:fs/promises';\nimport { resolve, basename, extname, relative } from 'node:path';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { minimatch } from 'minimatch';\nimport { SkillMetadata, SkillMetadataSchema, SkillLoadType, DiscoveredSkills } 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 // Enhanced properties\n alwaysApply: boolean;\n globs: string[];\n loadType: SkillLoadType;\n priority: number; // Lower = higher priority for deduplication\n sourceDir: string; // Which directory this skill came from\n}\n\nexport interface SkillWithContent extends Skill {\n content: string;\n}\n\n/**\n * Parse skill metadata from frontmatter\n * Handles YAML-like format including arrays for globs\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 // Parse YAML-like frontmatter\n const lines = frontmatter.split('\\n');\n const data: Record<string, unknown> = {};\n let currentArray: string[] | null = null;\n let currentArrayKey: string | null = null;\n \n for (const line of lines) {\n // Check if this is an array item (starts with -)\n if (currentArrayKey && line.trim().startsWith('-')) {\n let value = line.trim().slice(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 currentArray?.push(value);\n continue;\n }\n \n // Close any open array when we hit a non-array line\n if (currentArrayKey && currentArray) {\n data[currentArrayKey] = currentArray;\n currentArray = null;\n currentArrayKey = null;\n }\n \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 \n // Check if this starts an array (empty value followed by - items)\n if (value === '' || value === '[]') {\n currentArrayKey = key;\n currentArray = [];\n continue;\n }\n \n // Handle inline arrays like globs: [\"*.tsx\", \"*.jsx\"]\n if (value.startsWith('[') && value.endsWith(']')) {\n const arrayContent = value.slice(1, -1);\n const items = arrayContent.split(',').map(item => {\n let trimmed = item.trim();\n if ((trimmed.startsWith('\"') && trimmed.endsWith('\"')) ||\n (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))) {\n trimmed = trimmed.slice(1, -1);\n }\n return trimmed;\n }).filter(item => item.length > 0);\n data[key] = items;\n continue;\n }\n \n // Remove quotes if present\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n \n // Handle boolean values\n if (value === 'true') {\n data[key] = true;\n } else if (value === 'false') {\n data[key] = false;\n } else {\n data[key] = value;\n }\n }\n }\n \n // Close any remaining open array\n if (currentArrayKey && currentArray) {\n data[currentArrayKey] = currentArray;\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 * Options for loading skills from a directory\n */\ninterface LoadSkillsOptions {\n // Priority for deduplication (lower = higher priority)\n priority?: number;\n // Default load type if not specified in frontmatter\n defaultLoadType?: SkillLoadType;\n // Force alwaysApply for all skills in this directory\n forceAlwaysApply?: boolean;\n}\n\n/**\n * Load all skills from a directory (metadata only)\n */\nexport async function loadSkillsFromDirectory(\n directory: string,\n options: LoadSkillsOptions = {}\n): Promise<Skill[]> {\n const {\n priority = 50,\n defaultLoadType = 'on_demand',\n forceAlwaysApply = false,\n } = options;\n\n if (!existsSync(directory)) {\n return [];\n }\n\n const skills: Skill[] = [];\n const entries = await readdir(directory, { withFileTypes: true });\n\n for (const entry of entries) {\n // Handle both files and directories (for Claude-style SKILL.md in subdirs)\n let filePath: string;\n let fileName: string;\n\n if (entry.isDirectory()) {\n // Check for SKILL.md inside the directory (Claude format)\n const skillMdPath = resolve(directory, entry.name, 'SKILL.md');\n if (existsSync(skillMdPath)) {\n filePath = skillMdPath;\n fileName = entry.name;\n } else {\n continue;\n }\n } else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdc')) {\n filePath = resolve(directory, entry.name);\n fileName = entry.name;\n } else {\n continue;\n }\n\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n\n if (parsed) {\n const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;\n const loadType: SkillLoadType = alwaysApply ? 'always' : defaultLoadType;\n\n skills.push({\n name: parsed.metadata.name,\n description: parsed.metadata.description,\n filePath,\n alwaysApply,\n globs: parsed.metadata.globs,\n loadType,\n priority,\n sourceDir: directory,\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 alwaysApply: forceAlwaysApply,\n globs: [],\n loadType: forceAlwaysApply ? 'always' : defaultLoadType,\n priority,\n sourceDir: directory,\n });\n }\n }\n\n return skills;\n}\n\n/**\n * Load all skills from multiple directories (legacy function for backwards compatibility)\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 all skills from discovered directories with proper priority and typing\n */\nexport async function loadAllSkillsFromDiscovered(\n discovered: DiscoveredSkills\n): Promise<{ always: SkillWithContent[]; onDemand: Skill[]; all: Skill[] }> {\n const allSkills: Skill[] = [];\n const seenNames = new Set<string>();\n\n // Load from always-loaded directories (force alwaysApply = true)\n for (const { path, priority } of discovered.alwaysLoadedDirs) {\n const skills = await loadSkillsFromDirectory(path, {\n priority,\n defaultLoadType: 'always',\n forceAlwaysApply: true,\n });\n for (const skill of skills) {\n if (!seenNames.has(skill.name.toLowerCase())) {\n seenNames.add(skill.name.toLowerCase());\n allSkills.push(skill);\n }\n }\n }\n\n // Load from on-demand directories (respect frontmatter)\n for (const { path, priority } of discovered.onDemandDirs) {\n const skills = await loadSkillsFromDirectory(path, {\n priority,\n defaultLoadType: 'on_demand',\n forceAlwaysApply: false,\n });\n for (const skill of skills) {\n if (!seenNames.has(skill.name.toLowerCase())) {\n seenNames.add(skill.name.toLowerCase());\n allSkills.push(skill);\n }\n }\n }\n\n // Separate into always-loaded (with content) and on-demand\n const alwaysSkills = allSkills.filter(s => s.alwaysApply || s.loadType === 'always');\n const onDemandSkills = allSkills.filter(s => !s.alwaysApply && s.loadType !== 'always');\n\n // Load content for always-applied skills\n const alwaysWithContent: SkillWithContent[] = await Promise.all(\n alwaysSkills.map(async (skill) => {\n const content = await readFile(skill.filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n return {\n ...skill,\n content: parsed ? parsed.body : content,\n };\n })\n );\n\n return {\n always: alwaysWithContent,\n onDemand: onDemandSkills,\n all: allSkills,\n };\n}\n\n/**\n * Get skills that should be auto-injected based on glob patterns matching active files\n */\nexport async function getGlobMatchedSkills(\n skills: Skill[],\n activeFiles: string[],\n workingDirectory: string\n): Promise<SkillWithContent[]> {\n if (activeFiles.length === 0) {\n return [];\n }\n\n // Normalize active files to relative paths\n const relativeFiles = activeFiles.map(f => {\n if (f.startsWith(workingDirectory)) {\n return relative(workingDirectory, f);\n }\n return f;\n });\n\n // Find skills with matching globs that aren't already always-applied\n const matchedSkills = skills.filter(skill => {\n // Skip if already always applied (those are loaded separately)\n if (skill.alwaysApply || skill.loadType === 'always') {\n return false;\n }\n\n // Skip if no globs defined\n if (!skill.globs || skill.globs.length === 0) {\n return false;\n }\n\n // Check if any active file matches any glob\n return relativeFiles.some(file =>\n skill.globs.some(pattern => minimatch(file, pattern, { matchBase: true }))\n );\n });\n\n // Load content for matched skills\n const matchedWithContent: SkillWithContent[] = await Promise.all(\n matchedSkills.map(async (skill) => {\n const content = await readFile(skill.filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n return {\n ...skill,\n content: parsed ? parsed.body : content,\n loadType: 'glob_matched' as SkillLoadType,\n };\n })\n );\n\n return matchedWithContent;\n}\n\n/**\n * Load AGENTS.md content if it exists\n */\nexport async function loadAgentsMd(agentsMdPath: string | null): Promise<string | null> {\n if (!agentsMdPath || !existsSync(agentsMdPath)) {\n return null;\n }\n\n const content = await readFile(agentsMdPath, 'utf-8');\n return content;\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 on-demand skills list for context (shows as available to load)\n */\nexport function formatSkillsForContext(skills: Skill[]): string {\n // Filter to only on-demand skills\n const onDemandSkills = skills.filter(s => !s.alwaysApply && s.loadType !== 'always');\n\n if (onDemandSkills.length === 0) {\n return 'No on-demand skills available.';\n }\n\n const lines = ['Available skills (use load_skill tool to load into context):'];\n for (const skill of onDemandSkills) {\n const globInfo = skill.globs?.length ? ` [auto-loads for: ${skill.globs.join(', ')}]` : '';\n lines.push(`- ${skill.name}: ${skill.description}${globInfo}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format always-loaded skills content for injection into system prompt\n */\nexport function formatAlwaysLoadedSkills(skills: SkillWithContent[]): string {\n if (skills.length === 0) {\n return '';\n }\n\n const sections: string[] = [];\n \n for (const skill of skills) {\n sections.push(`### ${skill.name}\\n\\n${skill.content}`);\n }\n\n return `## Active Rules & Skills (Always Loaded)\\n\\n${sections.join('\\n\\n---\\n\\n')}`;\n}\n\n/**\n * Format glob-matched skills content for injection into system prompt\n */\nexport function formatGlobMatchedSkills(skills: SkillWithContent[]): string {\n if (skills.length === 0) {\n return '';\n }\n\n const sections: string[] = [];\n \n for (const skill of skills) {\n sections.push(`### ${skill.name}\\n\\n${skill.content}`);\n }\n\n return `## Context-Relevant Skills (Auto-loaded based on active files)\\n\\n${sections.join('\\n\\n---\\n\\n')}`;\n}\n\n/**\n * Format AGENTS.md content for injection\n */\nexport function formatAgentsMdContent(content: string | null): string {\n if (!content) {\n return '';\n }\n\n return `## Project Instructions (AGENTS.md)\\n\\n${content}`;\n}\n","import {\n streamText,\n generateText,\n tool,\n stepCountIs,\n type ToolSet,\n type ModelMessage,\n} from 'ai';\nimport { isAnthropicModel, resolveModel } from './model.js';\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, WriteFileProgress, SearchToolProgress } 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\n/** Attachment for user messages (images, files) */\nexport interface MessageAttachment {\n type: 'image' | 'file';\n data: string; // base64 data URL or raw base64\n mediaType?: string;\n filename?: string; // Original filename for context\n savedPath?: string; // Path where file was saved on disk\n}\n\nexport interface AgentRunOptions {\n prompt: string;\n /** Optional file/image attachments to include in the message */\n attachments?: MessageAttachment[];\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, write_file, or explore_agent) has progress to report */\n onToolProgress?: (progress: { toolName: string; data: BashToolProgress | WriteFileProgress | SearchToolProgress }) => 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 async createToolsWithCallbacks(options: {\n onToolProgress?: AgentRunOptions['onToolProgress'];\n }): Promise<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 onWriteFileProgress: options.onToolProgress\n ? (progress) => options.onToolProgress!({ toolName: 'write_file', data: progress })\n : undefined,\n onSearchProgress: options.onToolProgress\n ? (progress) => options.onToolProgress!({ toolName: 'explore_agent', 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 = await sessionQueries.getById(options.sessionId);\n if (!existing) {\n throw new Error(`Session not found: ${options.sessionId}`);\n }\n session = existing;\n } else {\n session = await 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 = await 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 * Build user message content from prompt and attachments\n */\n private buildUserMessageContent(\n prompt: string,\n attachments?: MessageAttachment[]\n ): string | Array<{ type: string; text?: string; image?: string; data?: string; mediaType?: string; filename?: string; savedPath?: string }> {\n if (!attachments || attachments.length === 0) {\n return prompt;\n }\n\n // Build content array with text and file parts\n const contentParts: Array<{ type: string; text?: string; image?: string; data?: string; mediaType?: string; filename?: string; savedPath?: string }> = [];\n \n // IMPORTANT: Put file location info FIRST so the model knows where files are saved\n // This gives the model context about file paths before it sees the images\n const attachmentDescriptions = attachments\n .map((a, i) => {\n const name = a.filename || `attachment_${i + 1}`;\n const typeLabel = a.type === 'image' ? 'Image' : 'File';\n const location = a.savedPath || '(path unknown)';\n return `${i + 1}. ${typeLabel}: \"${name}\" saved at: ${location}`;\n })\n .join('\\n');\n \n contentParts.push({ \n type: 'text', \n text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]\\n${attachmentDescriptions}\\n\\nYou can reference these files by their paths above. The file contents are also shown inline below.` \n });\n \n // Add user's text prompt\n if (prompt) {\n contentParts.push({ type: 'text', text: `\\n[USER MESSAGE]\\n${prompt}` });\n }\n \n // Add file/image parts with filename and path metadata\n for (const attachment of attachments) {\n if (attachment.type === 'image') {\n contentParts.push({\n type: 'image',\n image: attachment.data, // base64 data URL or raw base64\n mediaType: attachment.mediaType,\n filename: attachment.filename,\n savedPath: attachment.savedPath,\n });\n } else {\n contentParts.push({\n type: 'file',\n data: attachment.data,\n mediaType: attachment.mediaType || 'application/octet-stream',\n filename: attachment.filename,\n savedPath: attachment.savedPath,\n });\n }\n }\n \n return contentParts;\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 // Build user message content with attachments\n const userContent = this.buildUserMessageContent(options.prompt, options.attachments);\n\n // Add user message to context (skip if already saved externally)\n if (!options.skipSaveUserMessage) {\n this.context.addUserMessage(userContent);\n }\n\n // Update session status\n await sessionQueries.updateStatus(this.session.id, 'active');\n\n // Build system prompt with enhanced skill discovery\n const systemPrompt = await buildSystemPrompt({\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n sessionId: this.session.id,\n discoveredSkills: config.discoveredSkills,\n // TODO: Pass activeFiles from client for glob matching\n activeFiles: [],\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 ? await 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 useAnthropic = isAnthropicModel(this.session.model);\n const stream = streamText({\n model: resolveModel(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: useAnthropic\n ? {\n anthropic: {\n toolStreaming: true,\n thinking: {\n type: 'enabled',\n budgetTokens: 10000,\n },\n },\n }\n : undefined,\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 with enhanced skill discovery\n const systemPrompt = await buildSystemPrompt({\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n sessionId: this.session.id,\n discoveredSkills: config.discoveredSkills,\n activeFiles: [],\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 ? await 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 useAnthropic = isAnthropicModel(this.session.model);\n const result = await generateText({\n model: resolveModel(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: useAnthropic\n ? {\n anthropic: {\n thinking: {\n type: 'enabled',\n budgetTokens: 10000,\n },\n },\n }\n : undefined,\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, await execution);\n\n // Notify about approval requirement\n options.onApprovalRequired?.(await execution);\n\n // Update session status\n await 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 const exec = await execution;\n if (!approved) {\n // Tool was rejected\n const reason = resolverData?.reason || 'User rejected the tool execution';\n await toolExecutionQueries.reject(exec.id);\n await 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 await toolExecutionQueries.approve(exec.id);\n await sessionQueries.updateStatus(this.session.id, 'active');\n\n try {\n const result = await (originalTool as any).execute(input, toolOptions);\n await toolExecutionQueries.complete(exec.id, result);\n return result;\n } catch (error: any) {\n await toolExecutionQueries.complete(exec.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 = await toolExecutionQueries.getPendingApprovals(this.session.id);\n const execution = pendingFromDb.find((e: ToolExecution) => 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 await toolExecutionQueries.approve(execution.id);\n return { approved: true };\n }\n\n /**\n * Reject a pending tool execution\n */\n async reject(toolCallId: string, reason?: string): Promise<{ 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 = await toolExecutionQueries.getPendingApprovals(this.session.id);\n const execution = pendingFromDb.find((e: ToolExecution) => 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 await toolExecutionQueries.reject(execution.id);\n return { rejected: true };\n }\n\n /**\n * Get pending approvals\n */\n async getPendingApprovals(): Promise<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 { gateway } from '@ai-sdk/gateway';\nimport type { LanguageModel } from 'ai';\n\nconst ANTHROPIC_PREFIX = 'anthropic/';\nconst GOOGLE_PREFIX = 'google/';\n\n/**\n * Check if a model ID is an Anthropic model (for provider-specific options).\n */\nexport function isAnthropicModel(modelId: string): boolean {\n const normalized = modelId.trim().toLowerCase();\n return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith('claude-');\n}\n\n/**\n * Check if a model ID is a Google model.\n */\nexport function isGoogleModel(modelId: string): boolean {\n const normalized = modelId.trim().toLowerCase();\n return normalized.startsWith(GOOGLE_PREFIX) || normalized.startsWith('gemini-');\n}\n\n/**\n * Resolves a model ID to a LanguageModel instance.\n * \n * All models are routed through the Vercel AI Gateway.\n */\nexport function resolveModel(modelId: string): LanguageModel {\n return gateway(modelId.trim());\n}\n\n// Default models for subagents (smaller, faster models)\nexport const SUBAGENT_MODELS = {\n search: 'google/gemini-3-flash-preview',\n analyze: 'google/gemini-3-flash-preview',\n default: 'google/gemini-3-flash-preview',\n} as const;\n","/**\n * Remote database client\n * \n * Implements the same interface as the local SQLite database\n * but calls the remote server via HTTP.\n */\n\nimport type {\n Session,\n Message,\n ToolExecution,\n TodoItem,\n ModelMessage,\n Terminal,\n ActiveStream,\n Checkpoint,\n FileBackup,\n SubagentExecution,\n SubagentStep,\n IndexedChunk,\n IndexStatusRecord,\n LoadedSkill,\n} from './schema.js';\n\nlet remoteServerUrl: string | null = null;\nlet authKey: string | null = null;\n\n/**\n * Initialize the remote database client\n */\nexport function initRemoteDatabase(serverUrl: string, key: string) {\n remoteServerUrl = serverUrl.replace(/\\/$/, ''); // Remove trailing slash\n authKey = key;\n}\n\n/**\n * Close the remote client (no-op, just for API compatibility)\n */\nexport function closeRemoteDatabase() {\n remoteServerUrl = null;\n authKey = null;\n}\n\n/**\n * Check if remote database is configured\n */\nexport function isRemoteConfigured(): boolean {\n return !!remoteServerUrl && !!authKey;\n}\n\n/**\n * Date fields that should be parsed from ISO strings to Date objects.\n * These are top-level metadata fields on database records (Session, Message, etc.),\n * NOT fields inside modelMessage content (tool outputs, etc.).\n */\nconst DATE_FIELDS = ['createdAt', 'updatedAt', 'startedAt', 'completedAt', 'stoppedAt', 'finishedAt', 'loadedAt', 'indexedAt', 'lastFullIndex', 'lastIncrementalIndex'];\n\n/**\n * Fields that contain AI SDK ModelMessage data and should NOT be recursively\n * processed by parseDates. The AI SDK's Zod schema requires tool output values\n * to be valid JSON primitives (string, number, boolean, null, object, array).\n * Converting date strings to Date objects inside these fields corrupts them and\n * causes AI_InvalidPromptError when the messages are passed back to streamText().\n */\nconst MODEL_MESSAGE_FIELDS = ['modelMessage', 'modelMessages'];\n\n/**\n * Parse date strings to Date objects on top-level record fields only.\n * \n * IMPORTANT: Does NOT recurse into `modelMessage` / `modelMessages` fields.\n * Those contain AI SDK ModelMessage data that must remain JSON-serializable.\n * Recursing into them converts date strings (e.g. `createdAt` inside tool\n * result outputs) to Date objects, which violates the AI SDK's jsonValueSchema\n * and triggers AI_InvalidPromptError on subsequent streamText() calls.\n */\nfunction parseDates(obj: any): any {\n if (obj === null || obj === undefined) return obj;\n if (Array.isArray(obj)) return obj.map(parseDates);\n if (typeof obj !== 'object' || obj instanceof Date) return obj;\n \n const result = { ...obj };\n for (const key of Object.keys(result)) {\n // Skip modelMessage fields entirely - these must stay JSON-serializable\n if (MODEL_MESSAGE_FIELDS.includes(key)) {\n continue;\n }\n if (DATE_FIELDS.includes(key) && typeof result[key] === 'string') {\n result[key] = new Date(result[key]);\n } else if (typeof result[key] === 'object') {\n result[key] = parseDates(result[key]);\n }\n }\n return result;\n}\n\n/**\n * HTTP helper for remote API calls\n * @param options.skipParseDates - If true, skip the parseDates post-processing.\n * Use for endpoints that return ModelMessage[] directly, since those must\n * remain JSON-serializable for the AI SDK.\n */\nasync function api<T>(\n path: string,\n options: { method?: string; body?: unknown; skipParseDates?: boolean } = {}\n): Promise<T> {\n if (!remoteServerUrl || !authKey) {\n throw new Error('Remote database not initialized');\n }\n \n const url = `${remoteServerUrl}/db${path}`;\n const init: RequestInit = {\n method: options.method || 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${authKey}`,\n },\n };\n \n if (options.body) {\n init.body = JSON.stringify(options.body);\n }\n \n const response = await fetch(url, init);\n \n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n throw new Error(error.error || `HTTP ${response.status}`);\n }\n \n const text = await response.text();\n if (!text || text === 'null') {\n return null as T;\n }\n \n const parsed = JSON.parse(text);\n\n // Skip date parsing for raw ModelMessage data - it must stay JSON-serializable\n if (options.skipParseDates) {\n return parsed as T;\n }\n\n // Parse JSON and convert date strings to Date objects\n return parseDates(parsed) as T;\n}\n\n// ============================================\n// Session Queries\n// ============================================\n\nexport const remoteSessionQueries = {\n create(data: { workingDirectory: string; model: string; name?: string; config?: any }): Promise<Session> {\n return api<Session>('/sessions', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<Session | undefined> {\n return api<Session | undefined>(`/sessions/${id}`).catch(() => undefined);\n },\n\n list(limit = 50, offset = 0): Promise<Session[]> {\n return api<Session[]>(`/sessions?limit=${limit}&offset=${offset}`);\n },\n\n updateStatus(id: string, status: Session['status']): Promise<Session | undefined> {\n return api<Session | undefined>(`/sessions/${id}`, { method: 'PATCH', body: { status } });\n },\n\n updateModel(id: string, model: string): Promise<Session | undefined> {\n return api<Session | undefined>(`/sessions/${id}`, { method: 'PATCH', body: { model } });\n },\n\n update(id: string, updates: { model?: string; name?: string; config?: any }): Promise<Session | undefined> {\n return api<Session | undefined>(`/sessions/${id}`, { method: 'PATCH', body: updates });\n },\n\n delete(id: string): Promise<boolean> {\n return api<{ success: boolean }>(`/sessions/${id}`, { method: 'DELETE' }).then(r => r?.success ?? false);\n },\n};\n\n// ============================================\n// Message Queries\n// ============================================\n\nexport const remoteMessageQueries = {\n async getNextSequence(sessionId: string): Promise<number> {\n const result = await api<{ nextSequence: number }>(`/messages/session/${sessionId}/next-sequence`);\n return result.nextSequence;\n },\n\n create(sessionId: string, modelMessage: ModelMessage): Promise<Message> {\n return api<Message>('/messages', { method: 'POST', body: { sessionId, modelMessage } });\n },\n\n addMany(sessionId: string, modelMessages: ModelMessage[]): Promise<Message[]> {\n return api<Message[]>('/messages/batch', { method: 'POST', body: { sessionId, modelMessages } });\n },\n\n getBySession(sessionId: string): Promise<Message[]> {\n return api<Message[]>(`/messages/session/${sessionId}`);\n },\n\n getModelMessages(sessionId: string): Promise<ModelMessage[]> {\n // IMPORTANT: skipParseDates=true because ModelMessage data must remain\n // JSON-serializable. The parseDates function would convert date strings\n // inside tool result outputs (e.g. todo items with createdAt) to Date\n // objects, which violates the AI SDK's jsonValueSchema and causes\n // AI_InvalidPromptError on subsequent streamText() calls.\n return api<ModelMessage[]>(`/messages/session/${sessionId}/model-messages`, { skipParseDates: true });\n },\n\n async getRecentBySession(sessionId: string, limit = 50): Promise<Message[]> {\n const messages = await api<Message[]>(`/messages/session/${sessionId}`);\n return messages.slice(-limit);\n },\n\n async countBySession(sessionId: string): Promise<number> {\n const result = await api<{ count: number }>(`/messages/session/${sessionId}/count`);\n return result.count;\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/messages/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n\n async deleteFromSequence(sessionId: string, fromSequence: number): Promise<number> {\n const result = await api<{ deleted: number }>(\n `/messages/session/${sessionId}/from-sequence/${fromSequence}`,\n { method: 'DELETE' }\n );\n return result.deleted;\n },\n};\n\n// ============================================\n// Tool Execution Queries\n// ============================================\n\nexport const remoteToolExecutionQueries = {\n create(data: {\n sessionId: string;\n messageId?: string;\n toolName: string;\n toolCallId: string;\n input?: any;\n requiresApproval?: boolean;\n status?: 'pending' | 'approved' | 'rejected' | 'completed' | 'error';\n }): Promise<ToolExecution> {\n return api<ToolExecution>('/tool-executions', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/${id}`).catch(() => undefined);\n },\n\n getByToolCallId(toolCallId: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/by-tool-call-id/${toolCallId}`).catch(() => undefined);\n },\n\n getPendingApprovals(sessionId: string): Promise<ToolExecution[]> {\n return api<ToolExecution[]>(`/tool-executions/session/${sessionId}/pending`);\n },\n\n approve(id: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/${id}`, { method: 'PATCH', body: { status: 'approved' } });\n },\n\n reject(id: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/${id}`, { method: 'PATCH', body: { status: 'rejected' } });\n },\n\n complete(id: string, output: unknown, error?: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/${id}`, {\n method: 'PATCH',\n body: { status: error ? 'error' : 'completed', output, error },\n });\n },\n\n getBySession(sessionId: string): Promise<ToolExecution[]> {\n return api<ToolExecution[]>(`/tool-executions/session/${sessionId}`);\n },\n\n async deleteAfterTime(sessionId: string, afterTime: Date | string): Promise<number> {\n // Handle both Date objects and ISO strings\n const timestamp = afterTime instanceof Date ? afterTime.getTime() : new Date(afterTime).getTime();\n const result = await api<{ deleted: number }>(\n `/tool-executions/session/${sessionId}/after/${timestamp}`,\n { method: 'DELETE' }\n );\n return result.deleted;\n },\n};\n\n// ============================================\n// Todo Queries\n// ============================================\n\nexport const remoteTodoQueries = {\n create(data: { sessionId: string; content: string; order?: number }): Promise<TodoItem> {\n return api<TodoItem>('/todos', { method: 'POST', body: data });\n },\n\n createMany(sessionId: string, items: Array<{ content: string; order?: number }>): Promise<TodoItem[]> {\n return api<TodoItem[]>('/todos/batch', { method: 'POST', body: { sessionId, items } });\n },\n\n getBySession(sessionId: string): Promise<TodoItem[]> {\n return api<TodoItem[]>(`/todos/session/${sessionId}`);\n },\n\n updateStatus(id: string, status: TodoItem['status']): Promise<TodoItem | undefined> {\n return api<TodoItem | undefined>(`/todos/${id}`, { method: 'PATCH', body: { status } });\n },\n\n async delete(id: string): Promise<boolean> {\n const result = await api<{ success: boolean }>(`/todos/${id}`, { method: 'DELETE' });\n return result?.success ?? false;\n },\n\n async clearSession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/todos/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Skill Queries\n// ============================================\n\nexport const remoteSkillQueries = {\n load(sessionId: string, skillName: string): Promise<LoadedSkill> {\n return api<LoadedSkill>('/skills', { method: 'POST', body: { sessionId, skillName } });\n },\n\n getBySession(sessionId: string): Promise<LoadedSkill[]> {\n return api<LoadedSkill[]>(`/skills/session/${sessionId}`);\n },\n\n async isLoaded(sessionId: string, skillName: string): Promise<boolean> {\n const result = await api<{ isLoaded: boolean }>(`/skills/session/${sessionId}/is-loaded/${skillName}`);\n return result.isLoaded;\n },\n};\n\n// ============================================\n// Terminal Queries\n// ============================================\n\nexport const remoteTerminalQueries = {\n create(data: { sessionId: string; command: string; cwd: string; name?: string }): Promise<Terminal> {\n return api<Terminal>('/terminals', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<Terminal | undefined> {\n return api<Terminal | undefined>(`/terminals/${id}`).catch(() => undefined);\n },\n\n getBySession(sessionId: string): Promise<Terminal[]> {\n return api<Terminal[]>(`/terminals/session/${sessionId}`);\n },\n\n getRunning(sessionId: string): Promise<Terminal[]> {\n return api<Terminal[]>(`/terminals/session/${sessionId}/running`);\n },\n\n updateStatus(id: string, status: Terminal['status'], exitCode?: number, error?: string): Promise<Terminal | undefined> {\n return api<Terminal | undefined>(`/terminals/${id}`, { method: 'PATCH', body: { status, exitCode, error } });\n },\n\n updatePid(id: string, pid: number): Promise<Terminal | undefined> {\n return api<Terminal | undefined>(`/terminals/${id}`, { method: 'PATCH', body: { pid } });\n },\n\n async delete(id: string): Promise<boolean> {\n const result = await api<{ success: boolean }>(`/terminals/${id}`, { method: 'DELETE' });\n return result?.success ?? false;\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/terminals/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Active Stream Queries\n// ============================================\n\nexport const remoteActiveStreamQueries = {\n create(sessionId: string, streamId: string): Promise<ActiveStream> {\n return api<ActiveStream>('/streams', { method: 'POST', body: { sessionId, streamId } });\n },\n\n getBySessionId(sessionId: string): Promise<ActiveStream | undefined> {\n return api<ActiveStream | null>(`/streams/session/${sessionId}`).then(r => r ?? undefined);\n },\n\n getByStreamId(streamId: string): Promise<ActiveStream | undefined> {\n return api<ActiveStream | undefined>(`/streams/by-stream-id/${streamId}`).catch(() => undefined);\n },\n\n finish(streamId: string): Promise<ActiveStream | undefined> {\n return api<ActiveStream | undefined>(`/streams/by-stream-id/${streamId}`, { method: 'PATCH', body: { status: 'finished' } });\n },\n\n markError(streamId: string): Promise<ActiveStream | undefined> {\n return api<ActiveStream | undefined>(`/streams/by-stream-id/${streamId}`, { method: 'PATCH', body: { status: 'error' } });\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/streams/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Checkpoint Queries\n// ============================================\n\nexport const remoteCheckpointQueries = {\n create(data: { sessionId: string; messageSequence: number; gitHead?: string }): Promise<Checkpoint> {\n return api<Checkpoint>('/checkpoints', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<Checkpoint | undefined> {\n return api<Checkpoint | undefined>(`/checkpoints/${id}`).catch(() => undefined);\n },\n\n getBySession(sessionId: string): Promise<Checkpoint[]> {\n return api<Checkpoint[]>(`/checkpoints/session/${sessionId}`);\n },\n\n getByMessageSequence(sessionId: string, messageSequence: number): Promise<Checkpoint | undefined> {\n return api<Checkpoint | null>(`/checkpoints/session/${sessionId}/by-sequence/${messageSequence}`).then(r => r ?? undefined);\n },\n\n getLatest(sessionId: string): Promise<Checkpoint | undefined> {\n return api<Checkpoint | null>(`/checkpoints/session/${sessionId}/latest`).then(r => r ?? undefined);\n },\n\n async deleteAfterSequence(sessionId: string, messageSequence: number): Promise<number> {\n const result = await api<{ deleted: number }>(\n `/checkpoints/session/${sessionId}/after-sequence/${messageSequence}`,\n { method: 'DELETE' }\n );\n return result.deleted;\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/checkpoints/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// File Backup Queries\n// ============================================\n\nexport const remoteFileBackupQueries = {\n create(data: {\n checkpointId: string;\n sessionId: string;\n filePath: string;\n originalContent: string | null;\n existed: boolean;\n }): Promise<FileBackup> {\n return api<FileBackup>('/file-backups', { method: 'POST', body: data });\n },\n\n getByCheckpoint(checkpointId: string): Promise<FileBackup[]> {\n return api<FileBackup[]>(`/file-backups/checkpoint/${checkpointId}`);\n },\n\n getBySession(sessionId: string): Promise<FileBackup[]> {\n return api<FileBackup[]>(`/file-backups/session/${sessionId}`);\n },\n\n getFromSequence(sessionId: string, messageSequence: number): Promise<FileBackup[]> {\n return api<FileBackup[]>(`/file-backups/session/${sessionId}/from-sequence/${messageSequence}`);\n },\n\n async hasBackup(checkpointId: string, filePath: string): Promise<boolean> {\n const result = await api<{ hasBackup: boolean }>(\n `/file-backups/checkpoint/${checkpointId}/has-backup/${encodeURIComponent(filePath)}`\n );\n return result.hasBackup;\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/file-backups/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Subagent Queries\n// ============================================\n\nexport const remoteSubagentQueries = {\n create(data: {\n sessionId: string;\n toolCallId: string;\n subagentType: string;\n task: string;\n model: string;\n }): Promise<SubagentExecution> {\n return api<SubagentExecution>('/subagents', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}`).catch(() => undefined);\n },\n\n getByToolCallId(toolCallId: string): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/by-tool-call-id/${toolCallId}`).catch(() => undefined);\n },\n\n getBySession(sessionId: string): Promise<SubagentExecution[]> {\n return api<SubagentExecution[]>(`/subagents/session/${sessionId}`);\n },\n\n addStep(id: string, step: SubagentStep): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}/add-step`, { method: 'POST', body: { step } }).catch(() => undefined);\n },\n\n complete(id: string, result: unknown): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}`, { method: 'PATCH', body: { status: 'completed', result } }).catch(() => undefined);\n },\n\n markError(id: string, error: string): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}`, { method: 'PATCH', body: { status: 'error', error } }).catch(() => undefined);\n },\n\n cancel(id: string): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}`, { method: 'PATCH', body: { status: 'cancelled' } }).catch(() => undefined);\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/subagents/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Indexed Chunk Queries\n// ============================================\n\nexport const remoteIndexedChunkQueries = {\n upsert(\n _db: any, // Ignored - for API compatibility\n data: {\n id: string;\n contentHash: string;\n filePath: string;\n repoNamespace: string;\n startLine?: number;\n endLine?: number;\n language?: string;\n }\n ): Promise<IndexedChunk> {\n return api<IndexedChunk>('/indexed-chunks', { method: 'POST', body: data });\n },\n\n batchUpsert(\n _db: any,\n chunks: Array<{\n id: string;\n contentHash: string;\n filePath: string;\n repoNamespace: string;\n startLine?: number;\n endLine?: number;\n language?: string;\n }>\n ): Promise<{ created: number; updated: number }> {\n return api<{ created: number; updated: number }>('/indexed-chunks/batch', { \n method: 'POST', \n body: { chunks } \n });\n },\n\n getById(_db: any, id: string): Promise<IndexedChunk | undefined> {\n return api<IndexedChunk | undefined>(`/indexed-chunks/${id}`).catch(() => undefined);\n },\n\n getByNamespace(_db: any, namespace: string): Promise<IndexedChunk[]> {\n return api<IndexedChunk[]>(`/indexed-chunks/namespace/${namespace}`);\n },\n\n getByFilePath(_db: any, namespace: string, filePath: string): Promise<IndexedChunk[]> {\n return api<IndexedChunk[]>(`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`);\n },\n\n async deleteByNamespace(_db: any, namespace: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/indexed-chunks/namespace/${namespace}`, { method: 'DELETE' });\n return result.deleted;\n },\n\n async deleteByFilePath(_db: any, namespace: string, filePath: string): Promise<number> {\n const result = await api<{ deleted: number }>(\n `/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`,\n { method: 'DELETE' }\n );\n return result.deleted;\n },\n\n async countByNamespace(_db: any, namespace: string): Promise<number> {\n const result = await api<{ count: number }>(`/indexed-chunks/namespace/${namespace}/count`);\n return result.count;\n },\n};\n\n// ============================================\n// Index Status Queries\n// ============================================\n\nexport const remoteIndexStatusQueries = {\n upsert(\n _db: any, // Ignored\n data: {\n id: string;\n repoNamespace: string;\n totalChunks?: number;\n lastFullIndex?: Date;\n lastIncrementalIndex?: Date;\n }\n ): Promise<IndexStatusRecord> {\n return api<IndexStatusRecord>('/index-status', {\n method: 'POST',\n body: {\n ...data,\n lastFullIndex: data.lastFullIndex?.toISOString(),\n lastIncrementalIndex: data.lastIncrementalIndex?.toISOString(),\n },\n });\n },\n\n get(_db: any, namespace: string): Promise<IndexStatusRecord | undefined> {\n return api<IndexStatusRecord | null>(`/index-status/namespace/${namespace}`).then(r => r ?? undefined);\n },\n\n async delete(_db: any, namespace: string): Promise<boolean> {\n const result = await api<{ success: boolean }>(`/index-status/namespace/${namespace}`, { method: 'DELETE' });\n return result?.success ?? false;\n },\n\n list(_db: any): Promise<IndexStatusRecord[]> {\n return api<IndexStatusRecord[]>('/index-status');\n },\n};\n","/**\n * Database layer - Remote MongoDB only\n * \n * All data is stored on the remote server at agent.sparkecode.com\n */\n\nimport {\n initRemoteDatabase,\n closeRemoteDatabase,\n remoteSessionQueries,\n remoteMessageQueries,\n remoteToolExecutionQueries,\n remoteTodoQueries,\n remoteSkillQueries,\n remoteTerminalQueries,\n remoteActiveStreamQueries,\n remoteCheckpointQueries,\n remoteFileBackupQueries,\n remoteSubagentQueries,\n remoteIndexedChunkQueries,\n remoteIndexStatusQueries,\n} from './remote.js';\n\n// Re-export types from schema\nexport type {\n Session,\n NewSession,\n Message,\n NewMessage,\n ToolExecution,\n NewToolExecution,\n TodoItem,\n NewTodoItem,\n SessionConfig,\n ModelMessage,\n UserModelMessage,\n UserContentPart,\n UserTextPart,\n UserImagePart,\n UserFilePart,\n Terminal,\n NewTerminal,\n ActiveStream,\n NewActiveStream,\n Checkpoint,\n NewCheckpoint,\n FileBackup,\n NewFileBackup,\n SubagentExecution,\n NewSubagentExecution,\n SubagentStep,\n IndexedChunk,\n NewIndexedChunk,\n IndexStatusRecord,\n NewIndexStatusRecord,\n LoadedSkill,\n} from './schema.js';\n\nlet initialized = false;\n\n/**\n * Initialize the database with remote server config\n * @param config - Remote server configuration { url, authKey }\n */\nexport function initDatabase(config: { url: string; authKey: string }) {\n initRemoteDatabase(config.url, config.authKey);\n initialized = true;\n}\n\n/**\n * Get a stub database object for API compatibility\n * Functions that take a db parameter will ignore it for remote operations\n */\nexport function getDb() {\n if (!initialized) {\n throw new Error('Database not initialized. Call initDatabase first.');\n }\n // Return a stub - the actual queries use remote API calls\n return {} as any;\n}\n\n/**\n * Check if using remote database (always true now)\n */\nexport function isUsingRemote(): boolean {\n return true;\n}\n\n/**\n * Close the database connection\n */\nexport function closeDatabase() {\n closeRemoteDatabase();\n initialized = false;\n}\n\n// Re-export query objects with cleaner names\nexport const sessionQueries = remoteSessionQueries;\nexport const messageQueries = remoteMessageQueries;\nexport const toolExecutionQueries = remoteToolExecutionQueries;\nexport const todoQueries = remoteTodoQueries;\nexport const skillQueries = remoteSkillQueries;\nexport const terminalQueries = remoteTerminalQueries;\nexport const activeStreamQueries = remoteActiveStreamQueries;\nexport const checkpointQueries = remoteCheckpointQueries;\nexport const fileBackupQueries = remoteFileBackupQueries;\nexport const subagentQueries = remoteSubagentQueries;\nexport const indexedChunkQueries = remoteIndexedChunkQueries;\nexport const indexStatusQueries = remoteIndexStatusQueries;\n","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 DiscoveredSkills,\n ResolvedVectorGatewayConfig,\n ResolvedRemoteServerConfig,\n} from './types.js';\n\nconst CONFIG_FILE_NAMES = [\n 'sparkecoder.config.json',\n 'sparkecoder.json',\n '.sparkecoder.json',\n];\n\n/**\n * Discover all skill directories in the working directory\n * Searches for:\n * - .sparkecoder/rules/ (always loaded, priority 1)\n * - .sparkecoder/skills/ (on-demand, priority 2)\n * - .cursor/rules/ (parse frontmatter, priority 3)\n * - .claude/skills/ (on-demand, priority 4)\n * - skills/ (legacy, on-demand, priority 5)\n * - AGENTS.md (always loaded)\n */\nexport function discoverSkillDirectories(workingDir: string): DiscoveredSkills {\n const alwaysLoadedDirs: Array<{ path: string; priority: number }> = [];\n const onDemandDirs: Array<{ path: string; priority: number }> = [];\n const allDirectories: string[] = [];\n let agentsMdPath: string | null = null;\n\n // Priority 1: .sparkecoder/rules/ (always loaded)\n const sparkRulesDir = join(workingDir, '.sparkecoder', 'rules');\n if (existsSync(sparkRulesDir)) {\n alwaysLoadedDirs.push({ path: sparkRulesDir, priority: 1 });\n allDirectories.push(sparkRulesDir);\n }\n\n // Priority 2: .sparkecoder/skills/ (on-demand)\n const sparkSkillsDir = join(workingDir, '.sparkecoder', 'skills');\n if (existsSync(sparkSkillsDir)) {\n onDemandDirs.push({ path: sparkSkillsDir, priority: 2 });\n allDirectories.push(sparkSkillsDir);\n }\n\n // Priority 3: .cursor/rules/ (parse frontmatter for alwaysApply)\n const cursorRulesDir = join(workingDir, '.cursor', 'rules');\n if (existsSync(cursorRulesDir)) {\n // Cursor rules can be either - will be determined by frontmatter\n onDemandDirs.push({ path: cursorRulesDir, priority: 3 });\n allDirectories.push(cursorRulesDir);\n }\n\n // Priority 4: .claude/skills/ (on-demand)\n const claudeSkillsDir = join(workingDir, '.claude', 'skills');\n if (existsSync(claudeSkillsDir)) {\n onDemandDirs.push({ path: claudeSkillsDir, priority: 4 });\n allDirectories.push(claudeSkillsDir);\n }\n\n // Priority 5: skills/ (legacy, on-demand)\n const legacySkillsDir = join(workingDir, 'skills');\n if (existsSync(legacySkillsDir)) {\n onDemandDirs.push({ path: legacySkillsDir, priority: 5 });\n allDirectories.push(legacySkillsDir);\n }\n\n // Check for AGENTS.md (always loaded)\n const agentsMd = join(workingDir, 'AGENTS.md');\n if (existsSync(agentsMd)) {\n agentsMdPath = agentsMd;\n }\n\n // Also add built-in skills directory\n const builtInSkillsDir = resolve(dirname(import.meta.url.replace('file://', '')), '../skills/default');\n if (existsSync(builtInSkillsDir)) {\n onDemandDirs.push({ path: builtInSkillsDir, priority: 100 }); // Lowest priority\n allDirectories.push(builtInSkillsDir);\n }\n\n return {\n alwaysLoadedDirs,\n onDemandDirs,\n agentsMdPath,\n allDirectories,\n };\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 // Discover skill directories from standard locations\n const discovered = discoverSkillDirectories(resolvedWorkingDirectory);\n\n // Combine discovered directories with any additional configured directories\n const additionalDirs = (config.skills?.additionalDirectories || [])\n .map((dir) => resolve(configDir, dir))\n .filter((dir) => existsSync(dir));\n\n const resolvedSkillsDirectories = [\n ...discovered.allDirectories,\n ...additionalDirs,\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 // Resolve vector gateway config with env var overrides\n const resolvedVectorGateway: ResolvedVectorGatewayConfig = {\n redisUrl: process.env.VECTOR_REDIS_URL || config.vectorGateway?.redisUrl || null,\n httpUrl: process.env.VECTOR_HTTP_URL || config.vectorGateway?.httpUrl || null,\n embeddingModel:\n process.env.VECTOR_EMBEDDING_MODEL ||\n config.vectorGateway?.embeddingModel ||\n 'gemini-embedding-001',\n namespace: config.vectorGateway?.namespace || null,\n include: config.vectorGateway?.include || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.py',\n '**/*.go',\n '**/*.rs',\n '**/*.java',\n '**/*.md',\n '**/*.mdx',\n '**/*.txt',\n ],\n exclude: config.vectorGateway?.exclude || [\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/.git/**',\n '**/.next/**',\n '**/*.min.js',\n '**/*.bundle.js',\n '**/pnpm-lock.yaml',\n '**/package-lock.json',\n '**/yarn.lock',\n '**/.test-workspace/**',\n '**/.semantic-test-workspace/**',\n '**/.semantic-integration-test/**',\n ],\n };\n\n // Resolve remote server config with env var overrides\n // Default to production server when not explicitly configured\n const DEFAULT_REMOTE_URL = 'https://agent-remote-server.sparkecode.com';\n const remoteUrl = process.env.SPARKECODER_REMOTE_URL || config.remoteServer?.url || DEFAULT_REMOTE_URL;\n const remoteAuthKey = process.env.SPARKECODER_AUTH_KEY || config.remoteServer?.authKey || loadStoredAuthKey();\n \n const resolvedRemoteServer: ResolvedRemoteServerConfig = {\n url: remoteUrl,\n authKey: remoteAuthKey,\n isConfigured: !!remoteUrl && !!remoteAuthKey,\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 publicUrl: config.server.publicUrl,\n },\n resolvedWorkingDirectory,\n resolvedSkillsDirectories,\n resolvedDatabasePath,\n discoveredSkills: discovered,\n resolvedVectorGateway,\n resolvedRemoteServer,\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-6',\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// Auth Key Management (for remote server)\n// ============================================\n\nconst AUTH_KEY_FILE = 'auth-key.json';\n\ninterface StoredAuthKey {\n authKey: string;\n createdAt: string;\n userId?: string;\n}\n\n/**\n * Load stored auth key from app data directory\n */\nfunction loadStoredAuthKey(): string | null {\n const keysPath = join(getAppDataDirectory(), AUTH_KEY_FILE);\n if (!existsSync(keysPath)) {\n return null;\n }\n try {\n const content = readFileSync(keysPath, 'utf-8');\n const data = JSON.parse(content) as StoredAuthKey;\n return data.authKey || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Save auth key to app data directory\n */\nexport function saveAuthKey(authKey: string, userId?: string): void {\n const appDir = ensureAppDataDirectory();\n const keysPath = join(appDir, AUTH_KEY_FILE);\n const data: StoredAuthKey = {\n authKey,\n createdAt: new Date().toISOString(),\n userId,\n };\n writeFileSync(keysPath, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/**\n * Get stored auth key info\n */\nexport function getStoredAuthKeyInfo(): StoredAuthKey | null {\n const keysPath = join(getAppDataDirectory(), AUTH_KEY_FILE);\n if (!existsSync(keysPath)) {\n return null;\n }\n try {\n const content = readFileSync(keysPath, 'utf-8');\n return JSON.parse(content) as StoredAuthKey;\n } catch {\n return null;\n }\n}\n\n/**\n * Register with remote server and get new auth key\n */\nexport async function registerWithRemoteServer(\n serverUrl: string,\n name?: string\n): Promise<{ authKey: string; userId: string }> {\n const response = await fetch(`${serverUrl}/auth/register`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name: name || `CLI ${new Date().toISOString()}` }),\n });\n \n if (!response.ok) {\n const error = await response.json().catch(() => ({})) as { error?: string };\n throw new Error(error.error || `Failed to register: HTTP ${response.status}`);\n }\n \n const data = await response.json() as { authKey: string; userId: string };\n \n // Save the auth key\n saveAuthKey(data.authKey, data.userId);\n \n return data;\n}\n\n/**\n * Ensure we have a valid auth key for the remote server\n * If not configured, registers with the remote server to get one\n */\nexport async function ensureRemoteAuthKey(serverUrl: string): Promise<string> {\n // Check env var first\n if (process.env.SPARKECODER_AUTH_KEY) {\n return process.env.SPARKECODER_AUTH_KEY;\n }\n \n // Check stored key\n const storedKey = loadStoredAuthKey();\n if (storedKey) {\n return storedKey;\n }\n \n // Register with remote server\n const { authKey } = await registerWithRemoteServer(serverUrl);\n return authKey;\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 { 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\nTerminal output is stored in the global SparkECoder data directory. Use the \\`tail\\` option to read recent output.`,\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, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { nanoid } from 'nanoid';\nimport { getAppDataDirectory } from '../config/index.js';\n\nconst execAsync = promisify(exec);\n\n// Session prefix for all sparkecoder terminals\nconst SESSION_PREFIX = 'spark_';\n\n// Log directory base path - stored in global app data directory\nconst LOG_BASE_DIR = 'sessions';\n\nexport interface TerminalMeta {\n id: string;\n command: string;\n cwd: string;\n createdAt: string;\n sessionId: string;\n background: boolean;\n name?: string;\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 global terminal data directory\n * Uses OS-appropriate app data location (not the working directory)\n */\nfunction getTerminalDataDir(): string {\n const appDataDir = getAppDataDirectory();\n // Ensure directory exists\n if (!existsSync(appDataDir)) {\n mkdirSync(appDataDir, { recursive: true });\n }\n return appDataDir;\n}\n\n/**\n * Get the log directory for a terminal (stored in global app data, not working directory)\n */\nexport function getLogDir(terminalId: string, _workingDirectory: string, sessionId?: string): string {\n const baseDir = getTerminalDataDir();\n if (sessionId) {\n // Session-scoped path: ~/Library/Application Support/sparkecoder/sessions/{sessionId}/terminals/{terminalId}/\n return join(baseDir, LOG_BASE_DIR, sessionId, 'terminals', terminalId);\n }\n // Fallback for legacy terminals without sessionId\n return join(baseDir, '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; name?: 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 name: options.name,\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';\nimport * as LSP from '../lsp/index.js';\n\nexport interface WriteFileProgress {\n /** The file path being written */\n path: string;\n /** Relative path from working directory */\n relativePath: string;\n /** Write mode */\n mode: 'full' | 'str_replace';\n /** Status of the write operation */\n status: 'started' | 'content' | 'completed';\n /** For 'content' status: the content being written (may be chunked for streaming) */\n content?: string;\n /** When content is chunked, the chunk index (0-based) */\n chunkIndex?: number;\n /** When content is chunked, the total number of chunks */\n chunkCount?: number;\n /** When content is chunked, the start offset for this chunk */\n chunkStart?: number;\n /** Whether this content update is chunked */\n isChunked?: boolean;\n /** For str_replace: the old string being replaced */\n oldString?: string;\n /** For str_replace: the new string */\n newString?: string;\n /** Total content length (for progress tracking) */\n totalLength?: number;\n /** Action being performed */\n action?: 'created' | 'replaced' | 'edited';\n}\n\nexport interface WriteFileToolOptions {\n workingDirectory: string;\n sessionId: string;\n /** Enable LSP diagnostics after file edits (default: true) */\n enableLSP?: boolean;\n /** Called when write_file has progress to report (for streaming content) */\n onProgress?: (progress: WriteFileProgress) => void;\n}\n\nconst MAX_PROGRESS_CHUNK_SIZE = 16 * 1024;\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 const existed = existsSync(absolutePath);\n const action = existed ? 'replaced' : 'created';\n\n // Emit progress: started\n console.log('[WRITE-FILE] onProgress callback exists:', !!options.onProgress);\n console.log('[WRITE-FILE] Emitting started event for:', relativePath);\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'full',\n status: 'started',\n action,\n totalLength: content.length,\n });\n\n // Emit progress: content (chunked for large payloads to keep SSE stable)\n if (content.length <= MAX_PROGRESS_CHUNK_SIZE) {\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'full',\n status: 'content',\n content,\n action,\n totalLength: content.length,\n });\n } else {\n const chunkCount = Math.ceil(content.length / MAX_PROGRESS_CHUNK_SIZE);\n for (let i = 0; i < chunkCount; i += 1) {\n const chunkStart = i * MAX_PROGRESS_CHUNK_SIZE;\n const chunk = content.slice(chunkStart, chunkStart + MAX_PROGRESS_CHUNK_SIZE);\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'full',\n status: 'content',\n content: chunk,\n action,\n totalLength: content.length,\n chunkIndex: i,\n chunkCount,\n chunkStart,\n isChunked: true,\n });\n // Yield between chunks so SSE can flush progressively\n if (chunkCount > 1) {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\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 await writeFile(absolutePath, content, 'utf-8');\n\n // Get LSP diagnostics if enabled and file type is supported\n let diagnosticsOutput = '';\n if (options.enableLSP !== false && LSP.isSupported(absolutePath)) {\n await LSP.touchFile(absolutePath, true);\n diagnosticsOutput = await LSP.formatDiagnosticsOutput(absolutePath);\n }\n\n // Emit progress: completed\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'full',\n status: 'completed',\n action,\n totalLength: content.length,\n });\n\n return {\n success: true,\n path: absolutePath,\n relativePath,\n mode: 'full',\n action,\n bytesWritten: Buffer.byteLength(content, 'utf-8'),\n lineCount: content.split('\\n').length,\n ...(diagnosticsOutput && { diagnostics: diagnosticsOutput }),\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 // Emit progress: started\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'str_replace',\n status: 'started',\n action: 'edited',\n });\n\n // Emit progress: content (show the replacement)\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'str_replace',\n status: 'content',\n oldString: old_string,\n newString: new_string,\n action: 'edited',\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 // Get LSP diagnostics if enabled and file type is supported\n let diagnosticsOutput = '';\n if (options.enableLSP !== false && LSP.isSupported(absolutePath)) {\n await LSP.touchFile(absolutePath, true);\n diagnosticsOutput = await LSP.formatDiagnosticsOutput(absolutePath);\n }\n\n // Emit progress: completed\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'str_replace',\n status: 'completed',\n action: 'edited',\n });\n\n return {\n success: true,\n path: absolutePath,\n relativePath,\n mode: 'str_replace',\n linesRemoved: oldLines,\n linesAdded: newLines,\n lineDelta: newLines - oldLines,\n ...(diagnosticsOutput && { diagnostics: diagnosticsOutput }),\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 = await 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 (await 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 = await 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 = await 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 = await 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 = await 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 = await messageQueries.deleteFromSequence(sessionId, checkpoint.messageSequence);\n\n // Delete tool executions after the checkpoint was created\n await toolExecutionQueries.deleteAfterTime(sessionId, checkpoint.createdAt);\n\n // Delete checkpoints after this one (the file backups are deleted via CASCADE)\n const checkpointsDeleted = await 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 async function getCheckpoints(sessionId: string): Promise<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 = await 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 = await 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","/**\n * LSP Integration Module\n * \n * Provides Language Server Protocol support for the coding agent.\n * Automatically spawns LSP servers on-demand when files are touched,\n * collects diagnostics, and formats them for the agent.\n * \n * Usage:\n * import * as LSP from './lsp/index.js';\n * \n * // After editing a file, get diagnostics\n * await LSP.touchFile('/path/to/file.ts', true);\n * const diagnostics = await LSP.getDiagnostics('/path/to/file.ts');\n */\n\nimport { extname, dirname } from 'node:path';\nimport { getServerForExtension, getSupportedExtensions } from './servers.js';\nimport { createClient, normalizePath } from './client.js';\nimport {\n formatDiagnosticsForAgent,\n formatDiagnostic,\n DiagnosticSeverity,\n} from './types.js';\nimport type { Diagnostic, LSPClient } from './types.js';\n\n// Re-export types and utilities\nexport * from './types.js';\nexport { normalizePath } from './client.js';\nexport { getSupportedExtensions, getServerForExtension } from './servers.js';\n\n/**\n * Global state for LSP clients\n */\ninterface LSPState {\n clients: Map<string, LSPClient>; // key: `${serverId}:${root}`\n broken: Set<string>; // keys of servers that failed to start\n initialized: boolean;\n}\n\nlet state: LSPState = {\n clients: new Map(),\n broken: new Set(),\n initialized: false,\n};\n\n/**\n * Initialize the LSP system (optional, called automatically on first use)\n */\nexport async function init(): Promise<void> {\n if (state.initialized) return;\n state.initialized = true;\n}\n\n/**\n * Get or create an LSP client for a file\n */\nasync function getClientForFile(filePath: string): Promise<LSPClient | null> {\n const normalized = normalizePath(filePath);\n const ext = extname(normalized);\n \n // Check if we support this file type\n const serverDef = getServerForExtension(ext);\n if (!serverDef) {\n return null;\n }\n \n // Use file's directory as root (server will find project root)\n const root = dirname(normalized);\n const key = `${serverDef.id}:${root}`;\n \n // Check if we already have a client\n const existing = state.clients.get(key);\n if (existing) {\n return existing;\n }\n \n // Check if this server is broken for this root\n if (state.broken.has(key)) {\n return null;\n }\n \n // Spawn new server\n try {\n const handle = await serverDef.spawn(root);\n if (!handle) {\n state.broken.add(key);\n return null;\n }\n \n console.log(`[lsp] Started ${serverDef.name} for ${root}`);\n \n const client = await createClient(serverDef.id, handle, root);\n state.clients.set(key, client);\n \n // Handle process exit\n handle.process.on('exit', (code) => {\n console.log(`[lsp] ${serverDef.name} exited with code ${code}`);\n state.clients.delete(key);\n });\n \n return client;\n } catch (error) {\n console.error(`[lsp] Failed to start ${serverDef.name}:`, error);\n state.broken.add(key);\n return null;\n }\n}\n\n/**\n * Get all clients for a file (currently just TypeScript, but extensible)\n */\nasync function getClientsForFile(filePath: string): Promise<LSPClient[]> {\n const client = await getClientForFile(filePath);\n return client ? [client] : [];\n}\n\n/**\n * Touch a file (notify LSP of change and optionally wait for diagnostics)\n * \n * Call this after editing a file to get diagnostics.\n * \n * @param filePath - Path to the file\n * @param waitForDiagnostics - Whether to wait for diagnostics before returning\n * @returns Promise that resolves when done\n */\nexport async function touchFile(filePath: string, waitForDiagnostics = false): Promise<void> {\n const clients = await getClientsForFile(filePath);\n \n if (clients.length === 0) {\n return;\n }\n \n // Notify all clients\n await Promise.all(clients.map(client => client.notifyOpen(filePath)));\n \n // Optionally wait for diagnostics\n if (waitForDiagnostics) {\n await Promise.all(clients.map(client => client.waitForDiagnostics(filePath)));\n }\n}\n\n/**\n * Get diagnostics for a file\n */\nexport async function getDiagnostics(filePath: string): Promise<Diagnostic[]> {\n const normalized = normalizePath(filePath);\n const clients = await getClientsForFile(normalized);\n \n const allDiagnostics: Diagnostic[] = [];\n \n for (const client of clients) {\n const diags = client.getDiagnostics(normalized);\n allDiagnostics.push(...diags);\n }\n \n return allDiagnostics;\n}\n\n/**\n * Get all diagnostics from all clients\n */\nexport async function getAllDiagnostics(): Promise<Record<string, Diagnostic[]>> {\n const results: Record<string, Diagnostic[]> = {};\n \n for (const client of state.clients.values()) {\n const clientDiags = client.getAllDiagnostics();\n for (const [path, diagnostics] of clientDiags.entries()) {\n const existing = results[path] || [];\n existing.push(...diagnostics);\n results[path] = existing;\n }\n }\n \n return results;\n}\n\n/**\n * Wait for diagnostics on a file\n */\nexport async function waitForDiagnostics(filePath: string, timeoutMs = 5000): Promise<Diagnostic[]> {\n const normalized = normalizePath(filePath);\n const clients = await getClientsForFile(normalized);\n \n const allDiagnostics: Diagnostic[] = [];\n \n await Promise.all(\n clients.map(async (client) => {\n const diags = await client.waitForDiagnostics(normalized, timeoutMs);\n allDiagnostics.push(...diags);\n })\n );\n \n return allDiagnostics;\n}\n\n/**\n * Format diagnostics for agent output\n * \n * Call this after touchFile to get a formatted string to append to tool output.\n */\nexport async function formatDiagnosticsOutput(\n filePath: string,\n options: { maxDiagnostics?: number; errorsOnly?: boolean } = {}\n): Promise<string> {\n const diagnostics = await getDiagnostics(filePath);\n return formatDiagnosticsForAgent(filePath, diagnostics, options);\n}\n\n/**\n * Get errors only (severity = 1)\n */\nexport function getErrors(diagnostics: Diagnostic[]): Diagnostic[] {\n return diagnostics.filter(d => d.severity === DiagnosticSeverity.Error);\n}\n\n/**\n * Check if a file type is supported\n */\nexport function isSupported(filePath: string): boolean {\n const ext = extname(filePath);\n return getServerForExtension(ext) !== null;\n}\n\n/**\n * Shutdown all LSP clients\n */\nexport async function shutdown(): Promise<void> {\n const shutdownPromises: Promise<void>[] = [];\n \n for (const [key, client] of state.clients.entries()) {\n console.log(`[lsp] Shutting down ${key}`);\n shutdownPromises.push(client.shutdown());\n }\n \n await Promise.allSettled(shutdownPromises);\n \n state.clients.clear();\n state.broken.clear();\n state.initialized = false;\n}\n\n/**\n * Reset state (for testing)\n */\nexport function reset(): void {\n state = {\n clients: new Map(),\n broken: new Set(),\n initialized: false,\n };\n}\n\n// Utility exports for direct usage\nexport const DiagnosticUtils = {\n format: formatDiagnostic,\n formatForAgent: formatDiagnosticsForAgent,\n Severity: DiagnosticSeverity,\n};\n\n// Alias for backwards compatibility\nexport { DiagnosticUtils as Diagnostic };\n","/**\n * LSP Server Definitions\n * \n * Defines how to spawn and configure various LSP servers.\n * Currently supports TypeScript/JavaScript with typescript-language-server.\n */\n\nimport { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport type { LSPServerDefinition, LSPServerHandle } from './types.js';\n\n/**\n * Find the nearest directory containing one of the given files\n */\nfunction findNearestRoot(startDir: string, markers: string[]): string | null {\n let dir = startDir;\n const root = '/';\n \n while (dir !== root) {\n for (const marker of markers) {\n if (existsSync(resolve(dir, marker))) {\n return dir;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n \n return null;\n}\n\n/**\n * Check if a command exists in PATH\n */\nasync function commandExists(cmd: string): Promise<boolean> {\n try {\n const { exec } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execAsync = promisify(exec);\n \n const isWindows = process.platform === 'win32';\n const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;\n \n await execAsync(checkCmd);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * TypeScript/JavaScript Language Server\n * \n * Uses typescript-language-server which wraps tsserver.\n * Provides type checking, error detection, and more.\n */\nexport const TypeScriptServer: LSPServerDefinition = {\n id: 'typescript',\n name: 'TypeScript Language Server',\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'],\n \n async spawn(root: string): Promise<LSPServerHandle | null> {\n // Check for package manager lock files to determine project root\n const projectRoot = findNearestRoot(root, [\n 'package-lock.json',\n 'pnpm-lock.yaml',\n 'yarn.lock',\n 'bun.lockb',\n 'bun.lock',\n ]) || root;\n \n // Try to find typescript-language-server\n const hasNpx = await commandExists('npx');\n const hasBunx = await commandExists('bunx');\n const hasPnpx = await commandExists('pnpx');\n \n let cmd: string[];\n \n if (hasPnpx) {\n cmd = ['pnpx', 'typescript-language-server', '--stdio'];\n } else if (hasBunx) {\n cmd = ['bunx', 'typescript-language-server', '--stdio'];\n } else if (hasNpx) {\n cmd = ['npx', 'typescript-language-server', '--stdio'];\n } else {\n console.warn('[lsp] No package runner (npx/bunx/pnpx) found for typescript-language-server');\n return null;\n }\n \n try {\n const proc = spawn(cmd[0], cmd.slice(1), {\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: {\n ...process.env,\n // Suppress some noisy output\n TSS_LOG: '-level none',\n },\n });\n \n // Handle stderr (for debugging)\n proc.stderr?.on('data', (data) => {\n const msg = data.toString().trim();\n if (msg && !msg.includes('deprecated')) {\n // Only log non-trivial errors\n console.debug('[lsp:typescript:stderr]', msg);\n }\n });\n \n return {\n process: proc,\n initialization: {\n // TypeScript-specific initialization options\n preferences: {\n includeInlayParameterNameHints: 'none',\n includeInlayPropertyDeclarationTypeHints: false,\n includeInlayFunctionLikeReturnTypeHints: false,\n },\n },\n };\n } catch (error) {\n console.error('[lsp] Failed to spawn typescript-language-server:', error);\n return null;\n }\n },\n};\n\n/**\n * All available LSP servers\n */\nexport const servers: LSPServerDefinition[] = [\n TypeScriptServer,\n];\n\n/**\n * Get the appropriate server for a file extension\n */\nexport function getServerForExtension(ext: string): LSPServerDefinition | null {\n for (const server of servers) {\n if (server.extensions.includes(ext)) {\n return server;\n }\n }\n return null;\n}\n\n/**\n * Get all supported file extensions\n */\nexport function getSupportedExtensions(): string[] {\n const extensions = new Set<string>();\n for (const server of servers) {\n for (const ext of server.extensions) {\n extensions.add(ext);\n }\n }\n return Array.from(extensions);\n}\n","/**\n * LSP Client\n * \n * Manages communication with an LSP server via JSON-RPC over stdio.\n * Handles initialization, file notifications, and diagnostics collection.\n */\n\nimport {\n createMessageConnection,\n StreamMessageReader,\n StreamMessageWriter,\n type MessageConnection,\n} from 'vscode-jsonrpc/node.js';\nimport { pathToFileURL, fileURLToPath } from 'node:url';\nimport { readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { extname, normalize } from 'node:path';\nimport type { LSPClient, LSPServerHandle, Diagnostic } from './types.js';\n\n/**\n * Map file extension to LSP language ID\n */\nfunction getLanguageId(filePath: string): string {\n const ext = extname(filePath).toLowerCase();\n const map: Record<string, string> = {\n '.ts': 'typescript',\n '.tsx': 'typescriptreact',\n '.js': 'javascript',\n '.jsx': 'javascriptreact',\n '.mjs': 'javascript',\n '.cjs': 'javascript',\n '.mts': 'typescript',\n '.cts': 'typescript',\n '.json': 'json',\n '.jsonc': 'jsonc',\n };\n return map[ext] || 'plaintext';\n}\n\n/**\n * Normalize a file path for consistent key usage\n */\nexport function normalizePath(filePath: string): string {\n return normalize(filePath);\n}\n\n/**\n * Create an LSP client connected to a server\n */\nexport async function createClient(\n serverId: string,\n handle: LSPServerHandle,\n root: string\n): Promise<LSPClient> {\n const { process: proc } = handle;\n \n if (!proc.stdout || !proc.stdin) {\n throw new Error('LSP server process has no stdout/stdin');\n }\n \n // Create JSON-RPC connection over stdio\n const connection: MessageConnection = createMessageConnection(\n new StreamMessageReader(proc.stdout),\n new StreamMessageWriter(proc.stdin)\n );\n \n // Diagnostics storage\n const diagnostics = new Map<string, Diagnostic[]>();\n \n // Track open files and their versions\n const fileVersions = new Map<string, number>();\n \n // Event listeners for diagnostics updates\n const diagnosticListeners = new Map<string, Array<() => void>>();\n \n // Listen for diagnostic notifications\n connection.onNotification('textDocument/publishDiagnostics', (params: any) => {\n const filePath = normalizePath(fileURLToPath(params.uri));\n diagnostics.set(filePath, params.diagnostics || []);\n \n // Notify any waiters\n const listeners = diagnosticListeners.get(filePath);\n if (listeners) {\n for (const listener of listeners) {\n listener();\n }\n }\n });\n \n // Handle server requests\n connection.onRequest('workspace/configuration', async (params: any) => {\n // Return configuration for each requested section\n return params.items.map(() => handle.initialization || {});\n });\n \n connection.onRequest('client/registerCapability', async () => {\n // Accept capability registration\n return null;\n });\n \n connection.onRequest('window/workDoneProgress/create', async () => {\n // Accept progress token creation\n return null;\n });\n \n connection.onNotification('window/logMessage', (params: any) => {\n // Optionally log server messages\n if (params.type <= 2) { // Error or Warning\n console.debug(`[lsp:${serverId}]`, params.message);\n }\n });\n \n // Start listening\n connection.listen();\n \n // Initialize the server\n const initResult = await connection.sendRequest('initialize', {\n processId: process.pid,\n rootUri: pathToFileURL(root).href,\n rootPath: root,\n workspaceFolders: [\n {\n name: 'workspace',\n uri: pathToFileURL(root).href,\n },\n ],\n capabilities: {\n textDocument: {\n synchronization: {\n dynamicRegistration: true,\n willSave: false,\n willSaveWaitUntil: false,\n didSave: true,\n },\n publishDiagnostics: {\n relatedInformation: true,\n versionSupport: true,\n codeDescriptionSupport: true,\n },\n completion: {\n dynamicRegistration: true,\n completionItem: {\n snippetSupport: true,\n documentationFormat: ['markdown', 'plaintext'],\n },\n },\n hover: {\n dynamicRegistration: true,\n contentFormat: ['markdown', 'plaintext'],\n },\n definition: {\n dynamicRegistration: true,\n },\n references: {\n dynamicRegistration: true,\n },\n documentSymbol: {\n dynamicRegistration: true,\n },\n },\n workspace: {\n configuration: true,\n didChangeConfiguration: {\n dynamicRegistration: true,\n },\n didChangeWatchedFiles: {\n dynamicRegistration: true,\n },\n workspaceFolders: true,\n },\n },\n initializationOptions: handle.initialization,\n });\n \n // Send initialized notification\n await connection.sendNotification('initialized', {});\n \n // Return client interface\n const client: LSPClient = {\n serverId,\n root,\n diagnostics,\n \n async notifyOpen(filePath: string): Promise<void> {\n const normalized = normalizePath(filePath);\n \n if (!existsSync(normalized)) {\n return;\n }\n \n try {\n const content = await readFile(normalized, 'utf-8');\n const version = (fileVersions.get(normalized) ?? -1) + 1;\n fileVersions.set(normalized, version);\n \n if (version === 0) {\n // First time opening\n await connection.sendNotification('textDocument/didOpen', {\n textDocument: {\n uri: pathToFileURL(normalized).href,\n languageId: getLanguageId(normalized),\n version,\n text: content,\n },\n });\n } else {\n // Already open, send change\n await connection.sendNotification('textDocument/didChange', {\n textDocument: {\n uri: pathToFileURL(normalized).href,\n version,\n },\n contentChanges: [{ text: content }],\n });\n }\n } catch (error) {\n console.error('[lsp] Error notifying open:', error);\n }\n },\n \n async notifyChange(filePath: string): Promise<void> {\n const normalized = normalizePath(filePath);\n \n if (!existsSync(normalized)) {\n return;\n }\n \n try {\n const content = await readFile(normalized, 'utf-8');\n const version = (fileVersions.get(normalized) ?? 0) + 1;\n fileVersions.set(normalized, version);\n \n await connection.sendNotification('textDocument/didChange', {\n textDocument: {\n uri: pathToFileURL(normalized).href,\n version,\n },\n contentChanges: [{ text: content }],\n });\n } catch (error) {\n console.error('[lsp] Error notifying change:', error);\n }\n },\n \n async notifyClose(filePath: string): Promise<void> {\n const normalized = normalizePath(filePath);\n fileVersions.delete(normalized);\n diagnostics.delete(normalized);\n \n try {\n await connection.sendNotification('textDocument/didClose', {\n textDocument: {\n uri: pathToFileURL(normalized).href,\n },\n });\n } catch (error) {\n console.error('[lsp] Error notifying close:', error);\n }\n },\n \n async notifyWatchedFilesChanged(changes: Array<{ uri: string; type: number }>): Promise<void> {\n try {\n await connection.sendNotification('workspace/didChangeWatchedFiles', {\n changes,\n });\n } catch (error) {\n console.error('[lsp] Error notifying watched files:', error);\n }\n },\n \n async waitForDiagnostics(filePath: string, timeoutMs = 5000): Promise<Diagnostic[]> {\n const normalized = normalizePath(filePath);\n \n return new Promise<Diagnostic[]>((resolve) => {\n const startTime = Date.now();\n let debounceTimer: NodeJS.Timeout | undefined;\n let resolved = false;\n \n const cleanup = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n const listeners = diagnosticListeners.get(normalized);\n if (listeners) {\n const idx = listeners.indexOf(onDiagnostic);\n if (idx >= 0) listeners.splice(idx, 1);\n if (listeners.length === 0) {\n diagnosticListeners.delete(normalized);\n }\n }\n };\n \n const finish = () => {\n if (resolved) return;\n resolved = true;\n cleanup();\n resolve(diagnostics.get(normalized) || []);\n };\n \n const onDiagnostic = () => {\n // Debounce: wait 150ms after last update\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(finish, 150);\n };\n \n // Register listener\n if (!diagnosticListeners.has(normalized)) {\n diagnosticListeners.set(normalized, []);\n }\n diagnosticListeners.get(normalized)!.push(onDiagnostic);\n \n // Timeout fallback\n setTimeout(() => {\n if (!resolved) {\n finish();\n }\n }, timeoutMs);\n \n // If we already have diagnostics, trigger debounce\n if (diagnostics.has(normalized)) {\n onDiagnostic();\n }\n });\n },\n \n getDiagnostics(filePath: string): Diagnostic[] {\n return diagnostics.get(normalizePath(filePath)) || [];\n },\n \n getAllDiagnostics(): Map<string, Diagnostic[]> {\n return new Map(diagnostics);\n },\n \n async shutdown(): Promise<void> {\n try {\n await connection.sendRequest('shutdown');\n await connection.sendNotification('exit');\n connection.end();\n connection.dispose();\n proc.kill();\n } catch (error) {\n // Force kill if graceful shutdown fails\n proc.kill('SIGKILL');\n }\n },\n };\n \n return client;\n}\n","/**\n * LSP Types\n * \n * Type definitions for the Language Server Protocol integration.\n * These types are compatible with vscode-languageserver-types.\n */\n\n/**\n * Diagnostic severity levels from LSP spec\n */\nexport enum DiagnosticSeverity {\n Error = 1,\n Warning = 2,\n Information = 3,\n Hint = 4,\n}\n\n/**\n * Position in a text document (0-indexed)\n */\nexport interface Position {\n line: number;\n character: number;\n}\n\n/**\n * Range in a text document\n */\nexport interface Range {\n start: Position;\n end: Position;\n}\n\n/**\n * A diagnostic message from an LSP server\n */\nexport interface Diagnostic {\n range: Range;\n message: string;\n severity?: DiagnosticSeverity;\n code?: number | string;\n source?: string;\n relatedInformation?: DiagnosticRelatedInformation[];\n}\n\n/**\n * Related information for a diagnostic\n */\nexport interface DiagnosticRelatedInformation {\n location: {\n uri: string;\n range: Range;\n };\n message: string;\n}\n\n/**\n * Parameters for textDocument/publishDiagnostics notification\n */\nexport interface PublishDiagnosticsParams {\n uri: string;\n version?: number;\n diagnostics: Diagnostic[];\n}\n\n/**\n * LSP Server handle (spawned process)\n */\nexport interface LSPServerHandle {\n process: import('node:child_process').ChildProcess;\n initialization?: Record<string, unknown>;\n}\n\n/**\n * LSP Server definition\n */\nexport interface LSPServerDefinition {\n id: string;\n name: string;\n extensions: string[];\n spawn: (root: string) => Promise<LSPServerHandle | null>;\n}\n\n/**\n * LSP Client interface\n */\nexport interface LSPClient {\n serverId: string;\n root: string;\n diagnostics: Map<string, Diagnostic[]>;\n \n notifyOpen(filePath: string): Promise<void>;\n notifyChange(filePath: string): Promise<void>;\n notifyClose(filePath: string): Promise<void>;\n notifyWatchedFilesChanged(changes: Array<{ uri: string; type: number }>): Promise<void>;\n \n waitForDiagnostics(filePath: string, timeoutMs?: number): Promise<Diagnostic[]>;\n getDiagnostics(filePath: string): Diagnostic[];\n getAllDiagnostics(): Map<string, Diagnostic[]>;\n \n shutdown(): Promise<void>;\n}\n\n/**\n * Format a diagnostic for display to the agent\n */\nexport function formatDiagnostic(diagnostic: Diagnostic): string {\n const severity = {\n [DiagnosticSeverity.Error]: 'ERROR',\n [DiagnosticSeverity.Warning]: 'WARN',\n [DiagnosticSeverity.Information]: 'INFO',\n [DiagnosticSeverity.Hint]: 'HINT',\n }[diagnostic.severity ?? DiagnosticSeverity.Error];\n \n const line = diagnostic.range.start.line + 1; // Convert to 1-indexed\n const col = diagnostic.range.start.character + 1;\n const source = diagnostic.source ? ` [${diagnostic.source}]` : '';\n \n return `${severity} [${line}:${col}]${source} ${diagnostic.message}`;\n}\n\n/**\n * Format diagnostics for agent output\n */\nexport function formatDiagnosticsForAgent(\n filePath: string,\n diagnostics: Diagnostic[],\n options: { maxDiagnostics?: number; errorsOnly?: boolean } = {}\n): string {\n const { maxDiagnostics = 20, errorsOnly = true } = options;\n \n // Filter to errors only if requested\n const filtered = errorsOnly\n ? diagnostics.filter(d => d.severity === DiagnosticSeverity.Error)\n : diagnostics;\n \n if (filtered.length === 0) return '';\n \n const limited = filtered.slice(0, maxDiagnostics);\n const suffix = filtered.length > maxDiagnostics\n ? `\\n... and ${filtered.length - maxDiagnostics} more`\n : '';\n \n const formatted = limited.map(formatDiagnostic).join('\\n');\n \n return `\\n\\nLSP errors detected in this file, please fix:\\n<diagnostics file=\"${filePath}\">\\n${formatted}${suffix}\\n</diagnostics>`;\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 = await 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 = await todoQueries.getBySession(options.sessionId);\n \n const stats = {\n total: todos.length,\n pending: todos.filter((t: TodoItem) => t.status === 'pending').length,\n inProgress: todos.filter((t: TodoItem) => t.status === 'in_progress').length,\n completed: todos.filter((t: TodoItem) => t.status === 'completed').length,\n cancelled: todos.filter((t: TodoItem) => 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 = await 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 = await 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 (await 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 await 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","/**\n * Linter Tool\n * \n * Provides the agent with the ability to check files for lint/type errors\n * using the LSP (Language Server Protocol) integration.\n */\n\nimport { tool } from 'ai';\nimport { z } from 'zod';\nimport { resolve, relative, isAbsolute, extname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { readdir, stat } from 'node:fs/promises';\nimport * as LSP from '../lsp/index.js';\nimport type { Diagnostic } from '../lsp/types.js';\n\nexport interface LinterToolOptions {\n workingDirectory: string;\n}\n\nconst linterInputSchema = z.object({\n paths: z\n .array(z.string())\n .optional()\n .describe('File or directory paths to check for lint errors. If not provided, returns diagnostics for all recently touched files.'),\n fix: z\n .boolean()\n .optional()\n .default(false)\n .describe('Reserved for future use: auto-fix lint errors (not yet implemented)'),\n});\n\n/**\n * Recursively find all supported files in a directory\n */\nasync function findSupportedFiles(\n dir: string,\n workingDirectory: string,\n maxFiles = 50\n): Promise<string[]> {\n const files: string[] = [];\n const supportedExtensions = LSP.getSupportedExtensions();\n\n async function walk(currentDir: string) {\n if (files.length >= maxFiles) return;\n\n try {\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (files.length >= maxFiles) break;\n\n const fullPath = resolve(currentDir, entry.name);\n\n // Skip node_modules, .git, and other common ignore patterns\n if (entry.isDirectory()) {\n if (['node_modules', '.git', 'dist', 'build', '.next', 'coverage'].includes(entry.name)) {\n continue;\n }\n await walk(fullPath);\n } else if (entry.isFile()) {\n const ext = extname(entry.name);\n if (supportedExtensions.includes(ext)) {\n files.push(fullPath);\n }\n }\n }\n } catch {\n // Ignore permission errors etc.\n }\n }\n\n await walk(dir);\n return files;\n}\n\nexport function createLinterTool(options: LinterToolOptions) {\n return tool({\n description: `Check files for linting and type errors using the Language Server Protocol (LSP).\n\nSupports TypeScript, JavaScript, TSX, JSX files.\n\nUsage:\n- \\`linter({})\\` - Get diagnostics for all recently edited files\n- \\`linter({ paths: [\"src/app.ts\"] })\\` - Check specific files\n- \\`linter({ paths: [\"src/\"] })\\` - Check all supported files in a directory\n\nReturns detailed error information including line numbers, error messages, and severity.\nUse this after making changes to verify your code is correct, or proactively to find issues.\n\nWorking directory: ${options.workingDirectory}`,\n\n inputSchema: linterInputSchema,\n\n execute: async ({ paths }: z.infer<typeof linterInputSchema>) => {\n try {\n // If no paths provided, get all diagnostics from LSP\n if (!paths || paths.length === 0) {\n const allDiagnostics = await LSP.getAllDiagnostics();\n \n if (Object.keys(allDiagnostics).length === 0) {\n return {\n success: true,\n message: 'No lint errors found. No files have been analyzed yet - specify paths to check specific files.',\n files: [],\n totalErrors: 0,\n totalWarnings: 0,\n };\n }\n\n return formatDiagnosticsResult(allDiagnostics, options.workingDirectory);\n }\n\n // Process provided paths\n const filesToCheck: string[] = [];\n\n for (const path of paths) {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(options.workingDirectory, path);\n\n if (!existsSync(absolutePath)) {\n continue;\n }\n\n const stats = await stat(absolutePath);\n\n if (stats.isDirectory()) {\n const dirFiles = await findSupportedFiles(absolutePath, options.workingDirectory);\n filesToCheck.push(...dirFiles);\n } else if (stats.isFile()) {\n if (LSP.isSupported(absolutePath)) {\n filesToCheck.push(absolutePath);\n }\n }\n }\n\n if (filesToCheck.length === 0) {\n return {\n success: true,\n message: 'No supported files found to check. Supported extensions: ' + LSP.getSupportedExtensions().join(', '),\n files: [],\n totalErrors: 0,\n totalWarnings: 0,\n };\n }\n\n // Touch all files and wait for diagnostics\n await Promise.all(\n filesToCheck.map(file => LSP.touchFile(file, true))\n );\n\n // Collect diagnostics for all files\n const diagnosticsMap: Record<string, Diagnostic[]> = {};\n\n for (const file of filesToCheck) {\n const diagnostics = await LSP.getDiagnostics(file);\n if (diagnostics.length > 0) {\n diagnosticsMap[file] = diagnostics;\n }\n }\n\n return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\n/**\n * Format diagnostics into a structured result\n */\nfunction formatDiagnosticsResult(\n diagnosticsMap: Record<string, Diagnostic[]>,\n workingDirectory: string\n) {\n let totalErrors = 0;\n let totalWarnings = 0;\n let totalInfo = 0;\n\n const files: Array<{\n path: string;\n relativePath: string;\n errors: number;\n warnings: number;\n diagnostics: Array<{\n severity: string;\n line: number;\n column: number;\n message: string;\n source?: string;\n code?: string | number;\n }>;\n }> = [];\n\n for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {\n const relativePath = relative(workingDirectory, filePath);\n let fileErrors = 0;\n let fileWarnings = 0;\n\n const formattedDiagnostics = diagnostics.map(d => {\n const severity = getSeverityString(d.severity);\n \n if (d.severity === LSP.DiagnosticSeverity.Error) {\n fileErrors++;\n totalErrors++;\n } else if (d.severity === LSP.DiagnosticSeverity.Warning) {\n fileWarnings++;\n totalWarnings++;\n } else {\n totalInfo++;\n }\n\n return {\n severity,\n line: d.range.start.line + 1,\n column: d.range.start.character + 1,\n message: d.message,\n source: d.source,\n code: d.code,\n };\n });\n\n files.push({\n path: filePath,\n relativePath,\n errors: fileErrors,\n warnings: fileWarnings,\n diagnostics: formattedDiagnostics,\n });\n }\n\n // Sort by errors (most first)\n files.sort((a, b) => b.errors - a.errors);\n\n const hasIssues = totalErrors > 0 || totalWarnings > 0;\n\n return {\n success: true,\n message: hasIssues\n ? `Found ${totalErrors} error(s) and ${totalWarnings} warning(s) in ${files.length} file(s).`\n : `No lint errors found in ${Object.keys(diagnosticsMap).length || 'any'} file(s).`,\n files,\n totalErrors,\n totalWarnings,\n totalInfo,\n summary: hasIssues\n ? formatSummary(files)\n : undefined,\n };\n}\n\n/**\n * Get severity as a string\n */\nfunction getSeverityString(severity?: number): string {\n switch (severity) {\n case LSP.DiagnosticSeverity.Error:\n return 'error';\n case LSP.DiagnosticSeverity.Warning:\n return 'warning';\n case LSP.DiagnosticSeverity.Information:\n return 'info';\n case LSP.DiagnosticSeverity.Hint:\n return 'hint';\n default:\n return 'error';\n }\n}\n\n/**\n * Format a human-readable summary\n */\nfunction formatSummary(\n files: Array<{\n relativePath: string;\n diagnostics: Array<{\n severity: string;\n line: number;\n column: number;\n message: string;\n }>;\n }>\n): string {\n const lines: string[] = [];\n\n for (const file of files) {\n lines.push(`\\n${file.relativePath}:`);\n for (const d of file.diagnostics.slice(0, 10)) {\n const prefix = d.severity === 'error' ? '❌' : d.severity === 'warning' ? '⚠️' : 'ℹ️';\n lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);\n }\n if (file.diagnostics.length > 10) {\n lines.push(` ... and ${file.diagnostics.length - 10} more`);\n }\n }\n\n return lines.join('\\n');\n}\n\nexport type LinterTool = ReturnType<typeof createLinterTool>;\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { createSearchSubagent, SearchResult, SubagentProgressEvent } from '../agent/subagents/index.js';\nimport { truncateOutput } from '../utils/truncate.js';\n\nconst MAX_RESULT_CHARS = 8_000;\n\n/**\n * Progress event for the explore_agent tool (emitted via onProgress callback)\n */\nexport interface SearchToolProgress {\n status: 'started' | 'step' | 'complete' | 'error';\n subagentId?: string;\n stepType?: 'thought' | 'tool_call' | 'tool_result' | 'text';\n stepContent?: string;\n toolName?: string;\n toolInput?: unknown;\n toolOutput?: unknown;\n result?: SearchResult;\n error?: string;\n}\n\nexport interface SearchToolOptions {\n sessionId: string;\n workingDirectory: string;\n /** Callback for progress updates (for streaming to UI) */\n onProgress?: (progress: SearchToolProgress) => void | Promise<void>;\n}\n\n/**\n * Create the explore_agent tool that spawns a SearchSubagent\n * \n * This tool allows the main agent to delegate explore tasks to a specialized\n * mini-agent that uses a smaller, faster model (Gemini 3 Flash Preview).\n * \n * The subagent has access to:\n * - grep: Search for patterns in files\n * - glob: Find files by pattern\n * - read_file: Read file contents\n * - list_dir: List directory contents\n * \n * Progress is streamed back to the UI so users can see exploration happening.\n */\nexport function createSearchTool(options: SearchToolOptions) {\n return tool({\n description: `Delegate an explore task to the explore_agent tool. Use this when you need to:\n- Find files or code matching a pattern\n- Explore the codebase structure\n- Search for specific functions, classes, or variables\n- Understand how a feature is implemented\n\nThe Explore agent will explore the codebase and return a summary of findings.\nThis is more thorough than a simple grep because it can follow references and understand context.\n\nExamples:\n- \"Find all React components that use the useState hook\"\n- \"Where is the authentication logic implemented?\"\n- \"Find all API routes and their handlers\"\n- \"Search for usages of the UserService class\"`,\n\n inputSchema: z.object({\n query: z.string().describe('What to search for. Be specific about what you\\'re looking for.'),\n context: z.string().optional().describe('Optional additional context about why you need this information.'),\n }),\n\n execute: async ({ query, context }, toolOptions) => {\n const toolCallId = (toolOptions as any).toolCallId || `explore_agent_${Date.now()}`;\n \n // Emit started event\n await options.onProgress?.({\n status: 'started',\n subagentId: toolCallId,\n });\n \n try {\n const subagent = createSearchSubagent();\n \n // Build the full task\n const fullTask = context \n ? `${query}\\n\\nContext: ${context}`\n : query;\n \n // Run the subagent\n const result = await subagent.run({\n task: fullTask,\n sessionId: options.sessionId,\n toolCallId,\n workingDirectory: options.workingDirectory,\n onProgress: async (event: SubagentProgressEvent) => {\n // Map subagent events to explore_agent tool progress\n if (event.type === 'step' && event.step) {\n await options.onProgress?.({\n status: 'step',\n subagentId: event.subagentId,\n stepType: event.step.type,\n stepContent: event.step.content,\n toolName: event.step.toolName,\n toolInput: event.step.toolInput,\n toolOutput: event.step.toolOutput,\n });\n } else if (event.type === 'complete') {\n await options.onProgress?.({\n status: 'complete',\n subagentId: event.subagentId,\n result: event.result as SearchResult,\n });\n } else if (event.type === 'error') {\n await options.onProgress?.({\n status: 'error',\n subagentId: event.subagentId,\n error: event.error,\n });\n }\n },\n });\n \n if (!result.success) {\n return {\n success: false,\n error: result.error || 'Search failed',\n executionId: result.executionId,\n };\n }\n \n const searchResult = result.result!;\n \n // Format the result for the main agent\n let formattedResult = `## Explore Agent Results\\n\\n`;\n formattedResult += `**Summary:** ${searchResult.summary}\\n\\n`;\n \n if (searchResult.findings.length > 0) {\n formattedResult += `### Key Findings (${searchResult.findings.length} items)\\n\\n`;\n \n for (const finding of searchResult.findings) {\n if (finding.type === 'match') {\n formattedResult += `- **${finding.path}:${finding.lineNumber}** - ${truncateOutput(finding.content || '', 200)}\\n`;\n } else if (finding.type === 'file') {\n formattedResult += `- **${finding.path}** ${finding.context ? `(${finding.context})` : ''}\\n`;\n }\n }\n }\n \n formattedResult += `\\n**Stats:** ${searchResult.matchCount} matches across ${searchResult.filesSearched} files searched`;\n \n return {\n success: true,\n query: searchResult.query,\n summary: searchResult.summary,\n findings: searchResult.findings,\n matchCount: searchResult.matchCount,\n filesSearched: searchResult.filesSearched,\n formattedResult: truncateOutput(formattedResult, MAX_RESULT_CHARS),\n executionId: result.executionId,\n stepsCount: result.steps.length,\n };\n } catch (error: any) {\n await options.onProgress?.({\n status: 'error',\n error: error.message,\n });\n \n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\nexport type SearchTool = ReturnType<typeof createSearchTool>;\n","import {\n streamText,\n generateText,\n stepCountIs,\n type ToolSet,\n} from 'ai';\nimport { nanoid } from 'nanoid';\nimport { resolveModel, SUBAGENT_MODELS } from './model.js';\nimport { subagentQueries, SubagentExecution, SubagentStep } from '../db/index.js';\n\n/**\n * Progress event emitted by subagents\n */\nexport interface SubagentProgressEvent {\n type: 'step' | 'text' | 'tool_call' | 'tool_result' | 'complete' | 'error';\n subagentId: string;\n subagentType: string;\n step?: SubagentStep;\n text?: string;\n toolName?: string;\n toolInput?: unknown;\n toolOutput?: unknown;\n result?: unknown;\n error?: string;\n}\n\n/**\n * Options for running a subagent\n */\nexport interface SubagentRunOptions {\n task: string;\n sessionId: string;\n toolCallId: string;\n workingDirectory: string;\n /** Callback for progress events */\n onProgress?: (event: SubagentProgressEvent) => void | Promise<void>;\n /** Abort signal */\n abortSignal?: AbortSignal;\n}\n\n/**\n * Result from a subagent execution\n */\nexport interface SubagentResult<T = unknown> {\n success: boolean;\n result?: T;\n error?: string;\n steps: SubagentStep[];\n executionId: string;\n}\n\n/**\n * Base class for subagents.\n * \n * Subagents are lightweight agents that perform specific tasks using smaller,\n * faster models. They're spawned by the main agent via tools and report progress\n * back to the UI.\n * \n * To create a new subagent type:\n * 1. Extend this class\n * 2. Implement `getTools()` to return the tools available to this subagent\n * 3. Implement `getSystemPrompt()` to return the system prompt\n * 4. Optionally override `parseResult()` to structure the final output\n */\nexport abstract class Subagent<TResult = unknown> {\n /** Unique identifier for this subagent type */\n abstract readonly type: string;\n \n /** Human-readable name for this subagent */\n abstract readonly name: string;\n \n /** Model to use (defaults to gemini-3-flash-preview) */\n protected model: string;\n \n /** Maximum steps before stopping */\n protected maxSteps: number = 20;\n \n constructor(model?: string) {\n this.model = model || SUBAGENT_MODELS.default;\n }\n \n /**\n * Get the tools available to this subagent\n */\n protected abstract getTools(options: SubagentRunOptions): ToolSet;\n \n /**\n * Get the system prompt for this subagent\n */\n protected abstract getSystemPrompt(options: SubagentRunOptions): string;\n \n /**\n * Parse the final result from the subagent's output.\n * Override this to structure the result for your subagent type.\n */\n protected parseResult(text: string, steps: SubagentStep[]): TResult {\n return { text, steps } as TResult;\n }\n \n /**\n * Run the subagent with streaming progress updates\n */\n async run(options: SubagentRunOptions): Promise<SubagentResult<TResult>> {\n const { task, sessionId, toolCallId, onProgress, abortSignal } = options;\n const steps: SubagentStep[] = [];\n \n // Create execution record in database\n const execution = await subagentQueries.create({\n sessionId,\n toolCallId,\n subagentType: this.type,\n task,\n model: this.model,\n });\n \n const addStep = async (step: Omit<SubagentStep, 'id' | 'timestamp'>) => {\n const fullStep: SubagentStep = {\n id: nanoid(8),\n timestamp: Date.now(),\n ...step,\n };\n steps.push(fullStep);\n \n // Update database\n await subagentQueries.addStep(execution.id, fullStep);\n \n // Emit progress\n await onProgress?.({\n type: 'step',\n subagentId: execution.id,\n subagentType: this.type,\n step: fullStep,\n });\n };\n \n try {\n const tools = this.getTools(options);\n const systemPrompt = this.getSystemPrompt(options);\n \n // Run the subagent\n const result = await generateText({\n model: resolveModel(this.model) as any,\n system: systemPrompt,\n messages: [\n { role: 'user', content: task }\n ],\n tools,\n stopWhen: stepCountIs(this.maxSteps),\n abortSignal,\n onStepFinish: async (step) => {\n // Record text output\n if (step.text) {\n await addStep({\n type: 'text',\n content: step.text,\n });\n await onProgress?.({\n type: 'text',\n subagentId: execution.id,\n subagentType: this.type,\n text: step.text,\n });\n }\n \n // Record tool calls\n if (step.toolCalls) {\n for (const toolCall of step.toolCalls) {\n await addStep({\n type: 'tool_call',\n content: `Calling ${toolCall.toolName}`,\n toolName: toolCall.toolName,\n toolInput: toolCall.input,\n });\n await onProgress?.({\n type: 'tool_call',\n subagentId: execution.id,\n subagentType: this.type,\n toolName: toolCall.toolName,\n toolInput: toolCall.input,\n });\n }\n }\n \n // Record tool results\n if (step.toolResults) {\n for (const toolResult of step.toolResults) {\n await addStep({\n type: 'tool_result',\n content: `Result from ${toolResult.toolName}`,\n toolName: toolResult.toolName,\n toolOutput: toolResult.output,\n });\n await onProgress?.({\n type: 'tool_result',\n subagentId: execution.id,\n subagentType: this.type,\n toolName: toolResult.toolName,\n toolOutput: toolResult.output,\n });\n }\n }\n },\n });\n \n // Parse the final result\n const parsedResult = this.parseResult(result.text, steps);\n \n // Mark as complete\n await subagentQueries.complete(execution.id, parsedResult);\n \n await onProgress?.({\n type: 'complete',\n subagentId: execution.id,\n subagentType: this.type,\n result: parsedResult,\n });\n \n return {\n success: true,\n result: parsedResult,\n steps,\n executionId: execution.id,\n };\n } catch (error: any) {\n const errorMessage = error.message || 'Unknown error';\n \n // Mark as error\n await subagentQueries.markError(execution.id, errorMessage);\n \n await onProgress?.({\n type: 'error',\n subagentId: execution.id,\n subagentType: this.type,\n error: errorMessage,\n });\n \n return {\n success: false,\n error: errorMessage,\n steps,\n executionId: execution.id,\n };\n }\n }\n \n /**\n * Run with streaming (for real-time progress in UI)\n */\n async *stream(options: SubagentRunOptions): AsyncGenerator<SubagentProgressEvent> {\n const events: SubagentProgressEvent[] = [];\n let resolveNext: ((event: SubagentProgressEvent | null) => void) | null = null;\n let done = false;\n \n // Queue for events\n const eventQueue: SubagentProgressEvent[] = [];\n \n // Start the run with progress callback\n const runPromise = this.run({\n ...options,\n onProgress: async (event) => {\n eventQueue.push(event);\n if (resolveNext) {\n resolveNext(eventQueue.shift()!);\n resolveNext = null;\n }\n },\n }).then((result) => {\n done = true;\n if (resolveNext) {\n resolveNext(null);\n }\n return result;\n });\n \n // Yield events as they come\n while (!done || eventQueue.length > 0) {\n if (eventQueue.length > 0) {\n yield eventQueue.shift()!;\n } else if (!done) {\n // Wait for next event\n const event = await new Promise<SubagentProgressEvent | null>((resolve) => {\n resolveNext = resolve;\n });\n if (event) {\n yield event;\n }\n }\n }\n \n // Wait for completion\n await runPromise;\n }\n}\n\n// Export types\nexport type { SubagentStep };\n","import { tool, type ToolSet } from 'ai';\nimport { z } from 'zod';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { readFile, stat, readdir } from 'node:fs/promises';\nimport { resolve, relative, isAbsolute, join, extname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { Subagent, SubagentRunOptions, SubagentStep } from '../subagent.js';\nimport { SUBAGENT_MODELS } from '../model.js';\nimport { truncateOutput } from '../../utils/truncate.js';\n\nconst execAsync = promisify(exec);\n\nconst MAX_OUTPUT_CHARS = 20_000;\nconst MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB for explore subagent\n\n/**\n * Search result from the Explore agent\n */\nexport interface SearchResult {\n query: string;\n summary: string;\n findings: SearchFinding[];\n filesSearched: number;\n matchCount: number;\n}\n\nexport interface SearchFinding {\n type: 'file' | 'match' | 'pattern';\n path: string;\n content?: string;\n lineNumber?: number;\n relevance: 'high' | 'medium' | 'low';\n context?: string;\n}\n\n/**\n * SearchSubagent - A mini-agent specialized for exploring codebases.\n * \n * Uses a smaller, faster model (Gemini 3 Flash Preview) and has access to:\n * - grep: Search for patterns in files\n * - glob: Find files by pattern\n * - read_file: Read file contents\n * - list_dir: List directory contents\n * \n * Returns structured search results to the main agent.\n */\nexport class SearchSubagent extends Subagent<SearchResult> {\n readonly type = 'search';\n readonly name = 'Explore Agent';\n \n constructor(model?: string) {\n super(model || SUBAGENT_MODELS.search);\n this.maxSteps = 15; // Limit steps for search tasks\n }\n \n protected getSystemPrompt(options: SubagentRunOptions): string {\n return `You are an Explore agent for codebase discovery. Your job is to map the structure, find relevant files, and surface connections based on the user's query.\n\nWorking Directory: ${options.workingDirectory}\n\nYou have these tools available:\n- grep: Search for patterns in files using ripgrep (rg)\n- glob: Find files matching a pattern\n- read_file: Read contents of a specific file\n- list_dir: List directory contents\n\n## Strategy - Explore in Parallel\n\nIMPORTANT: When exploring, run MULTIPLE searches in PARALLEL to cover different variations and related terms. Don't explore sequentially - batch your searches together!\n\nFor example, if asked \"how does authentication work\":\n- Search for \"auth\", \"login\", \"session\", \"jwt\", \"token\" all at once\n- Search in different likely directories: src/auth/, lib/auth/, services/auth/\n- Look for common patterns: AuthProvider, useAuth, authenticate, isAuthenticated\n\n**Parallel Search Patterns:**\n1. Try multiple naming conventions at once:\n - camelCase: \"getUserData\", \"handleAuth\"\n - snake_case: \"get_user_data\", \"handle_auth\"\n - PascalCase: \"UserService\", \"AuthProvider\"\n\n2. Search for related concepts together:\n - For \"database\": search \"db\", \"database\", \"query\", \"model\", \"schema\"\n - For \"api\": search \"endpoint\", \"route\", \"handler\", \"controller\", \"api\"\n\n3. Use glob AND grep together:\n - Find files: \\`*.auth.ts\\`, \\`*Auth*.tsx\\`, \\`auth/*.ts\\`\n - Search content: patterns, function names, class names\n\n## Execution Flow\n1. Start broad: list likely directories or entry points if the query is open-ended\n2. Run 2-4 parallel searches covering different angles of the query\n3. Review results and identify the most relevant files\n4. Read the key files to understand the full context\n5. Provide a clear summary with exact file paths and line numbers\n\nBe efficient - you have limited steps. Maximize coverage with parallel tool calls.\n\n## Output Format\nWhen done, provide a summary with:\n- Key files/locations found (with full paths)\n- Relevant code snippets showing the important parts\n- How the pieces connect together\n- Suggested next files to inspect (if more depth is needed)\n\nKeep your responses concise and focused on actionable information.`;\n }\n \n protected getTools(options: SubagentRunOptions): ToolSet {\n const workingDirectory = options.workingDirectory;\n \n return {\n grep: tool({\n description: 'Search for patterns in files using ripgrep. Returns matching lines with file paths and line numbers.',\n inputSchema: z.object({\n pattern: z.string().describe('The regex pattern to search for'),\n path: z.string().optional().describe('Subdirectory or file to search in (relative to working directory)'),\n fileType: z.string().optional().describe('File type to filter (e.g., \"ts\", \"js\", \"py\")'),\n maxResults: z.number().optional().default(50).describe('Maximum number of results to return'),\n }),\n execute: async ({ pattern, path, fileType, maxResults }) => {\n try {\n const searchPath = path \n ? resolve(workingDirectory, path) \n : workingDirectory;\n \n let args = ['rg', '--line-number', '--no-heading'];\n \n if (fileType) {\n args.push('--type', fileType);\n }\n \n args.push('--max-count', String(maxResults || 50));\n args.push('--', pattern, searchPath);\n \n const { stdout, stderr } = await execAsync(args.join(' '), {\n cwd: workingDirectory,\n maxBuffer: 5 * 1024 * 1024,\n timeout: 30000,\n });\n \n const output = truncateOutput(stdout || 'No matches found', MAX_OUTPUT_CHARS);\n const matchCount = (stdout || '').split('\\n').filter(Boolean).length;\n \n return {\n success: true,\n output,\n matchCount,\n pattern,\n };\n } catch (error: any) {\n // rg returns exit code 1 when no matches found\n if (error.code === 1 && !error.stderr) {\n return {\n success: true,\n output: 'No matches found',\n matchCount: 0,\n pattern,\n };\n }\n return {\n success: false,\n error: error.message,\n pattern,\n };\n }\n },\n }),\n \n glob: tool({\n description: 'Find files matching a glob pattern. Returns list of matching file paths.',\n inputSchema: z.object({\n pattern: z.string().describe('Glob pattern (e.g., \"**/*.ts\", \"src/**/*.tsx\", \"*.json\")'),\n maxResults: z.number().optional().default(100).describe('Maximum number of files to return'),\n }),\n execute: async ({ pattern, maxResults }) => {\n try {\n // Use find command with pattern matching\n const { stdout } = await execAsync(\n `find . -type f -name \"${pattern.replace('**/', '')}\" 2>/dev/null | head -n ${maxResults || 100}`,\n {\n cwd: workingDirectory,\n timeout: 30000,\n }\n );\n \n const files = stdout.trim().split('\\n').filter(Boolean);\n \n return {\n success: true,\n files,\n count: files.length,\n pattern,\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n pattern,\n };\n }\n },\n }),\n \n read_file: tool({\n description: 'Read the contents of a file. Use this to examine specific files found in search.',\n inputSchema: z.object({\n path: z.string().describe('Path to the file (relative to working directory or absolute)'),\n startLine: z.number().optional().describe('Start reading from this line (1-indexed)'),\n endLine: z.number().optional().describe('Stop reading at this line (1-indexed, inclusive)'),\n }),\n execute: async ({ path, startLine, endLine }) => {\n try {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(workingDirectory, path);\n \n if (!existsSync(absolutePath)) {\n return {\n success: false,\n error: `File not found: ${path}`,\n };\n }\n \n const stats = await stat(absolutePath);\n if (stats.size > MAX_FILE_SIZE) {\n return {\n success: false,\n error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`,\n };\n }\n \n let content = await readFile(absolutePath, 'utf-8');\n \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 content = lines.slice(start, end).join('\\n');\n }\n \n return {\n success: true,\n path: relative(workingDirectory, absolutePath),\n content: truncateOutput(content, MAX_OUTPUT_CHARS),\n lineCount: content.split('\\n').length,\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n }),\n \n list_dir: tool({\n description: 'List contents of a directory. Shows files and subdirectories.',\n inputSchema: z.object({\n path: z.string().optional().default('.').describe('Directory path (relative to working directory)'),\n recursive: z.boolean().optional().default(false).describe('List recursively (be careful with large directories)'),\n maxDepth: z.number().optional().default(2).describe('Maximum depth for recursive listing'),\n }),\n execute: async ({ path, recursive, maxDepth }) => {\n try {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(workingDirectory, path);\n \n if (!existsSync(absolutePath)) {\n return {\n success: false,\n error: `Directory not found: ${path}`,\n };\n }\n \n const stats = await stat(absolutePath);\n if (!stats.isDirectory()) {\n return {\n success: false,\n error: `Not a directory: ${path}`,\n };\n }\n \n if (recursive) {\n // Use find for recursive listing\n const { stdout } = await execAsync(\n `find . -maxdepth ${maxDepth} -type f 2>/dev/null | head -n 200`,\n {\n cwd: absolutePath,\n timeout: 10000,\n }\n );\n \n const files = stdout.trim().split('\\n').filter(Boolean);\n return {\n success: true,\n path: relative(workingDirectory, absolutePath) || '.',\n files,\n count: files.length,\n recursive: true,\n };\n } else {\n const entries = await readdir(absolutePath, { withFileTypes: true });\n const items = entries.slice(0, 200).map(e => ({\n name: e.name,\n type: e.isDirectory() ? 'directory' : 'file',\n }));\n \n return {\n success: true,\n path: relative(workingDirectory, absolutePath) || '.',\n items,\n count: items.length,\n };\n }\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n }),\n };\n }\n \n protected parseResult(text: string, steps: SubagentStep[]): SearchResult {\n // Extract findings from tool results\n const findings: SearchFinding[] = [];\n let filesSearched = 0;\n let matchCount = 0;\n \n for (const step of steps) {\n if (step.type === 'tool_result' && step.toolOutput) {\n const output = step.toolOutput as any;\n \n if (step.toolName === 'grep' && output.success) {\n matchCount += output.matchCount || 0;\n \n // Parse grep output to extract findings\n const lines = (output.output || '').split('\\n').filter(Boolean).slice(0, 10);\n for (const line of lines) {\n // Format: path:line:content\n const match = line.match(/^([^:]+):(\\d+):(.*)$/);\n if (match) {\n findings.push({\n type: 'match',\n path: match[1],\n lineNumber: parseInt(match[2], 10),\n content: match[3].trim(),\n relevance: 'high',\n });\n }\n }\n } else if (step.toolName === 'glob' && output.success) {\n filesSearched += output.count || 0;\n \n for (const file of (output.files || []).slice(0, 5)) {\n findings.push({\n type: 'file',\n path: file,\n relevance: 'medium',\n });\n }\n } else if (step.toolName === 'read_file' && output.success) {\n findings.push({\n type: 'file',\n path: output.path,\n relevance: 'high',\n context: `${output.lineCount} lines`,\n });\n }\n }\n }\n \n // Get the query from the first user message (task)\n const query = steps.length > 0 \n ? (steps.find(s => s.type === 'text')?.content || '')\n : '';\n \n return {\n query,\n summary: text,\n findings: findings.slice(0, 20), // Limit findings\n filesSearched,\n matchCount,\n };\n }\n}\n\n// Factory function\nexport function createSearchSubagent(model?: string): SearchSubagent {\n return new SearchSubagent(model);\n}\n","/**\n * Semantic Search Tool\n * Uses Vector Gateway to perform semantic similarity search on indexed codebase\n */\n\nimport { tool } from 'ai';\nimport { z } from 'zod';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { minimatch } from 'minimatch';\nimport {\n getVectorClient,\n closeVectorClient,\n getRepoNamespace,\n getEmbeddingModel,\n} from '../semantic/index.js';\nimport { getConfig } from '../config/index.js';\nimport { SemanticMatch } from '../semantic/types.js';\n\nexport interface SemanticSearchToolOptions {\n workingDirectory: string;\n}\n\nexport interface SemanticSearchResult {\n success: boolean;\n query?: string;\n matches?: SemanticMatch[];\n totalMatches?: number;\n duration?: number;\n error?: string;\n}\n\nconst semanticSearchInputSchema = z.object({\n query: z\n .string()\n .describe('Natural language search query describing what you want to find'),\n topK: z\n .number()\n .optional()\n .default(10)\n .describe('Number of results to return (default: 10, max: 50)'),\n filePattern: z\n .string()\n .optional()\n .describe('Filter results by file glob pattern (e.g., \"*.ts\", \"src/**/*.py\")'),\n language: z\n .string()\n .optional()\n .describe('Filter by programming language (e.g., \"typescript\", \"python\")'),\n});\n\n/**\n * Create the semantic_search tool\n */\nexport function createSemanticSearchTool(options: SemanticSearchToolOptions) {\n return tool({\n description: `Search the codebase using semantic similarity. This tool finds code by understanding its meaning, not just matching text.\n\nUse this tool when:\n- You need to understand how something works in the codebase\n- You're looking for code related to a concept (e.g., \"authentication\", \"database queries\")\n- You want to find implementations of features\n- The user asks \"where is X?\" or \"how does Y work?\"\n\nThis tool requires the repository to be indexed first with 'sparkecoder index'.\n\nReturns matching code snippets with file paths, line numbers, and relevance scores.`,\n\n inputSchema: semanticSearchInputSchema,\n\n execute: async ({\n query,\n topK,\n filePattern,\n language,\n }: z.infer<typeof semanticSearchInputSchema>): Promise<SemanticSearchResult> => {\n const startTime = Date.now();\n\n try {\n const config = getConfig();\n\n const namespace = await getRepoNamespace(\n options.workingDirectory,\n config.resolvedVectorGateway.namespace\n );\n\n if (!namespace) {\n return {\n success: false,\n error: 'Repository namespace not found. Ensure this is a git repository with a remote configured.',\n };\n }\n\n const client = getVectorClient();\n if (!client) {\n return {\n success: false,\n error: 'Remote server not configured. Set SPARKECODER_REMOTE_URL/SPARKECODER_AUTH_KEY or run sparkecoder to register.',\n };\n }\n\n try {\n const limitedTopK = Math.min(Math.max(1, topK), 50);\n\n const embeddingModel = getEmbeddingModel();\n const result = await client.search.queryAndWait(query, {\n namespace,\n topK: limitedTopK * 2,\n includeMetadata: true,\n embeddingModel,\n });\n\n const matches: SemanticMatch[] = [];\n\n for (const match of result.matches) {\n const metadata = match.metadata as Record<string, unknown> | undefined;\n if (!metadata) continue;\n\n const filePath = metadata.filePath as string;\n const startLine = metadata.startLine as number;\n const endLine = metadata.endLine as number;\n const matchLanguage = metadata.language as string;\n const symbolName = metadata.symbolName as string | undefined;\n\n if (filePattern) {\n const matchesPattern = minimatch(filePath, filePattern, { dot: true });\n if (!matchesPattern) continue;\n }\n\n if (language && matchLanguage !== language.toLowerCase()) {\n continue;\n }\n\n const fullPath = join(options.workingDirectory, filePath);\n if (!existsSync(fullPath)) {\n continue;\n }\n\n let snippet = '';\n try {\n const content = readFileSync(fullPath, 'utf-8');\n const lines = content.split('\\n');\n const snippetLines = lines.slice(\n Math.max(0, startLine - 1),\n Math.min(lines.length, endLine)\n );\n snippet = snippetLines.join('\\n');\n\n if (snippet.length > 500) {\n snippet = snippet.slice(0, 500) + '\\n... (truncated)';\n }\n } catch {\n // Ignore read errors\n }\n\n matches.push({\n filePath,\n startLine,\n endLine,\n score: match.score,\n snippet,\n symbolName,\n language: matchLanguage,\n });\n\n if (matches.length >= limitedTopK) {\n break;\n }\n }\n\n return {\n success: true,\n query,\n matches,\n totalMatches: matches.length,\n duration: Date.now() - startTime,\n };\n } finally {\n await closeVectorClient();\n }\n } catch (error) {\n return {\n success: false,\n error: `Semantic search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n },\n });\n}\n","/**\n * Git remote to namespace resolution\n * Converts git remote URLs to TurboPuffer namespaces\n */\n\nimport { execSync } from 'node:child_process';\n\n/**\n * Get the git remote URL for a repository\n * Returns null if not a git repo or no remote configured\n */\nexport function getGitRemoteUrl(workingDirectory: string): string | null {\n try {\n const result = execSync('git remote get-url origin', {\n cwd: workingDirectory,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Parse a git remote URL to extract org and repo name\n * Supports:\n * - https://github.com/org/repo.git\n * - git@github.com:org/repo.git\n * - https://gitlab.com/org/repo\n * - ssh://git@bitbucket.org/org/repo.git\n */\nexport function parseGitRemoteUrl(url: string): { org: string; repo: string } | null {\n // Remove .git suffix if present\n const cleanUrl = url.replace(/\\.git$/, '');\n\n // Try SSH format: git@github.com:org/repo\n const sshMatch = cleanUrl.match(/git@[^:]+:([^/]+)\\/(.+)$/);\n if (sshMatch) {\n return { org: sshMatch[1], repo: sshMatch[2] };\n }\n\n // Try HTTPS format: https://github.com/org/repo\n const httpsMatch = cleanUrl.match(/https?:\\/\\/[^/]+\\/([^/]+)\\/(.+)$/);\n if (httpsMatch) {\n return { org: httpsMatch[1], repo: httpsMatch[2] };\n }\n\n // Try SSH with protocol: ssh://git@github.com/org/repo\n const sshProtoMatch = cleanUrl.match(/ssh:\\/\\/[^/]+\\/([^/]+)\\/(.+)$/);\n if (sshProtoMatch) {\n return { org: sshProtoMatch[1], repo: sshProtoMatch[2] };\n }\n\n return null;\n}\n\n/**\n * Sanitize a string for use in a namespace\n * - Lowercase\n * - Replace non-alphanumeric with underscores\n * - Remove leading/trailing underscores\n * - Collapse multiple underscores\n */\nfunction sanitizeForNamespace(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '_')\n .replace(/^_+|_+$/g, '')\n .replace(/_+/g, '_');\n}\n\n/**\n * Get the namespace for a repository\n * Format: sparkecoder_{org}_{repo}\n * \n * @param workingDirectory - The repo working directory\n * @param configuredNamespace - Optional namespace override from config\n * @returns The namespace string, or null if not a git repo\n */\nexport async function getRepoNamespace(\n workingDirectory: string,\n configuredNamespace?: string | null\n): Promise<string | null> {\n // Use configured namespace if provided\n if (configuredNamespace) {\n return configuredNamespace;\n }\n\n // Get git remote URL\n const remoteUrl = getGitRemoteUrl(workingDirectory);\n if (!remoteUrl) {\n return null;\n }\n\n // Parse org and repo\n const parsed = parseGitRemoteUrl(remoteUrl);\n if (!parsed) {\n return null;\n }\n\n // Build namespace\n const org = sanitizeForNamespace(parsed.org);\n const repo = sanitizeForNamespace(parsed.repo);\n return `sparkecoder_${org}_${repo}`;\n}\n\n/**\n * Check if the working directory is a git repository\n */\nexport function isGitRepository(workingDirectory: string): boolean {\n try {\n execSync('git rev-parse --git-dir', {\n cwd: workingDirectory,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the current git branch name\n */\nexport function getCurrentBranch(workingDirectory: string): string | null {\n try {\n const result = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: workingDirectory,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Get the current git commit hash (short form)\n */\nexport function getCurrentCommit(workingDirectory: string): string | null {\n try {\n const result = execSync('git rev-parse --short HEAD', {\n cwd: workingDirectory,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.trim();\n } catch {\n return null;\n }\n}\n","/**\n * Content hashing utilities for deduplication\n * Uses SHA-256 for deterministic content hashing\n */\n\nimport { createHash } from 'node:crypto';\n\n/**\n * Compute SHA-256 hash of content\n * Returns first 16 characters of hex digest for a reasonable ID length\n */\nexport function computeContentHash(content: string): string {\n const hash = createHash('sha256');\n hash.update(content, 'utf-8');\n return hash.digest('hex').slice(0, 16);\n}\n\n/**\n * Compute a full SHA-256 hash (64 chars)\n */\nexport function computeFullHash(content: string): string {\n const hash = createHash('sha256');\n hash.update(content, 'utf-8');\n return hash.digest('hex');\n}\n\n/**\n * Generate a chunk ID from content hash and chunk index\n * Format: {contentHash}_{chunkIndex}\n */\nexport function generateChunkId(contentHash: string, chunkIndex: number): string {\n return `${contentHash}_${chunkIndex}`;\n}\n\n/**\n * Parse a chunk ID to extract content hash and chunk index\n */\nexport function parseChunkId(chunkId: string): { contentHash: string; chunkIndex: number } | null {\n const match = chunkId.match(/^([a-f0-9]+)_(\\d+)$/);\n if (!match) {\n return null;\n }\n return {\n contentHash: match[1],\n chunkIndex: parseInt(match[2], 10),\n };\n}\n\n/**\n * Compute hash for a file's content\n * Normalizes line endings for consistent hashing across platforms\n */\nexport function computeFileHash(content: string): string {\n // Normalize line endings to LF\n const normalized = content.replace(/\\r\\n/g, '\\n');\n return computeContentHash(normalized);\n}\n","/**\n * Hybrid code chunking\n * - Semantic chunking for code files (by function/class)\n * - Sliding window for documentation and text files\n */\n\nimport { extname, basename } from 'node:path';\nimport { Chunk, ChunkMetadata, ChunkType } from './types.js';\nimport { computeContentHash, generateChunkId } from './hasher.js';\n\n// Language detection by file extension\nconst LANGUAGE_MAP: Record<string, string> = {\n '.ts': 'typescript',\n '.tsx': 'typescript',\n '.js': 'javascript',\n '.jsx': 'javascript',\n '.mjs': 'javascript',\n '.cjs': 'javascript',\n '.py': 'python',\n '.go': 'go',\n '.rs': 'rust',\n '.java': 'java',\n '.kt': 'kotlin',\n '.swift': 'swift',\n '.rb': 'ruby',\n '.php': 'php',\n '.c': 'c',\n '.cpp': 'cpp',\n '.h': 'c',\n '.hpp': 'cpp',\n '.cs': 'csharp',\n '.md': 'markdown',\n '.mdx': 'markdown',\n '.txt': 'text',\n '.json': 'json',\n '.yaml': 'yaml',\n '.yml': 'yaml',\n '.toml': 'toml',\n '.xml': 'xml',\n '.html': 'html',\n '.css': 'css',\n '.scss': 'scss',\n '.less': 'less',\n '.sql': 'sql',\n '.sh': 'shell',\n '.bash': 'shell',\n '.zsh': 'shell',\n};\n\n// Languages that support semantic chunking\nconst SEMANTIC_LANGUAGES = new Set([\n 'typescript',\n 'javascript',\n 'python',\n 'go',\n 'rust',\n 'java',\n 'kotlin',\n 'swift',\n 'ruby',\n 'php',\n 'c',\n 'cpp',\n 'csharp',\n]);\n\n// Sliding window config\nconst SLIDING_WINDOW_SIZE = 1500; // ~500 tokens\nconst SLIDING_WINDOW_OVERLAP = 300; // ~100 tokens\n\n// Max chunk size (to avoid very long embeddings)\nconst MAX_CHUNK_SIZE = 4000; // ~1300 tokens\n\n/**\n * Detect language from file path\n */\nexport function detectLanguage(filePath: string): string {\n const ext = extname(filePath).toLowerCase();\n return LANGUAGE_MAP[ext] || 'unknown';\n}\n\n/**\n * Check if a language supports semantic chunking\n */\nexport function supportsSemanticChunking(language: string): boolean {\n return SEMANTIC_LANGUAGES.has(language);\n}\n\n/**\n * Chunk a file into embedding-ready chunks\n */\nexport function chunkFile(filePath: string, content: string): Chunk[] {\n const language = detectLanguage(filePath);\n \n // Skip empty files\n if (!content.trim()) {\n return [];\n }\n\n // Use semantic chunking for supported languages, sliding window otherwise\n if (supportsSemanticChunking(language)) {\n return chunkCodeSemantic(filePath, content, language);\n } else {\n return chunkSlidingWindow(filePath, content, language);\n }\n}\n\n/**\n * Semantic chunking for code files\n * Extracts functions, classes, and significant blocks\n */\nfunction chunkCodeSemantic(filePath: string, content: string, language: string): Chunk[] {\n const chunks: Chunk[] = [];\n const lines = content.split('\\n');\n \n // Simple regex-based extraction (tree-sitter would be more accurate)\n // This is a pragmatic approach that works for most cases\n \n const blocks = extractCodeBlocks(lines, language);\n \n if (blocks.length === 0) {\n // Fall back to sliding window if no blocks found\n return chunkSlidingWindow(filePath, content, language);\n }\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i];\n const blockContent = lines.slice(block.startLine, block.endLine + 1).join('\\n');\n \n // Skip very small blocks\n if (blockContent.trim().length < 50) {\n continue;\n }\n\n // If block is too large, split it\n if (blockContent.length > MAX_CHUNK_SIZE) {\n const subChunks = splitLargeBlock(filePath, blockContent, block.startLine, language, block.type, block.name);\n chunks.push(...subChunks);\n } else {\n const contentHash = computeContentHash(blockContent);\n const chunkId = generateChunkId(contentHash, i);\n \n chunks.push({\n id: chunkId,\n text: buildChunkText(filePath, blockContent, block.name),\n contentHash,\n chunkIndex: i,\n metadata: {\n filePath,\n startLine: block.startLine + 1, // 1-indexed\n endLine: block.endLine + 1,\n language,\n chunkType: block.type as ChunkType,\n symbolName: block.name,\n },\n });\n }\n }\n\n // If no meaningful chunks, fall back to sliding window\n if (chunks.length === 0) {\n return chunkSlidingWindow(filePath, content, language);\n }\n\n return reindexChunks(chunks);\n}\n\n/**\n * Extract code blocks (functions, classes) from source code\n */\nfunction extractCodeBlocks(\n lines: string[],\n language: string\n): Array<{ startLine: number; endLine: number; type: string; name?: string }> {\n const blocks: Array<{ startLine: number; endLine: number; type: string; name?: string }> = [];\n \n // Language-specific patterns\n const patterns = getLanguagePatterns(language);\n \n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n \n // Check for function/class definitions\n for (const pattern of patterns) {\n const match = line.match(pattern.regex);\n if (match) {\n const name = match[1];\n const endLine = findBlockEnd(lines, i, language);\n \n blocks.push({\n startLine: i,\n endLine,\n type: pattern.type,\n name,\n });\n \n i = endLine + 1;\n break;\n }\n }\n \n i++;\n }\n\n // Merge adjacent small blocks\n return mergeSmallBlocks(blocks, lines);\n}\n\n/**\n * Get regex patterns for extracting code blocks\n */\nfunction getLanguagePatterns(language: string): Array<{ regex: RegExp; type: string }> {\n switch (language) {\n case 'typescript':\n case 'javascript':\n return [\n { regex: /^\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)/, type: 'function' },\n { regex: /^\\s*(?:export\\s+)?(?:const|let|var)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?(?:\\([^)]*\\)|[^=])\\s*=>/, type: 'function' },\n { regex: /^\\s*(?:export\\s+)?class\\s+(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:export\\s+)?interface\\s+(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:export\\s+)?type\\s+(\\w+)/, type: 'class' },\n ];\n case 'python':\n return [\n { regex: /^\\s*(?:async\\s+)?def\\s+(\\w+)/, type: 'function' },\n { regex: /^\\s*class\\s+(\\w+)/, type: 'class' },\n ];\n case 'go':\n return [\n { regex: /^\\s*func\\s+(?:\\([^)]+\\)\\s+)?(\\w+)/, type: 'function' },\n { regex: /^\\s*type\\s+(\\w+)\\s+struct/, type: 'class' },\n { regex: /^\\s*type\\s+(\\w+)\\s+interface/, type: 'class' },\n ];\n case 'rust':\n return [\n { regex: /^\\s*(?:pub\\s+)?(?:async\\s+)?fn\\s+(\\w+)/, type: 'function' },\n { regex: /^\\s*(?:pub\\s+)?struct\\s+(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:pub\\s+)?impl\\s+(?:<[^>]+>\\s+)?(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:pub\\s+)?trait\\s+(\\w+)/, type: 'class' },\n ];\n case 'java':\n case 'kotlin':\n return [\n { regex: /^\\s*(?:public|private|protected)?\\s*(?:static\\s+)?(?:\\w+\\s+)?(\\w+)\\s*\\(/, type: 'function' },\n { regex: /^\\s*(?:public|private|protected)?\\s*(?:abstract\\s+)?class\\s+(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:public|private|protected)?\\s*interface\\s+(\\w+)/, type: 'class' },\n ];\n default:\n return [\n { regex: /^\\s*(?:function|def|fn|func)\\s+(\\w+)/, type: 'function' },\n { regex: /^\\s*class\\s+(\\w+)/, type: 'class' },\n ];\n }\n}\n\n/**\n * Find the end of a code block (matching braces/indentation)\n */\nfunction findBlockEnd(lines: string[], startLine: number, language: string): number {\n // Python uses indentation\n if (language === 'python') {\n return findPythonBlockEnd(lines, startLine);\n }\n \n // Most languages use braces\n return findBraceBlockEnd(lines, startLine);\n}\n\n/**\n * Find block end for brace-based languages\n */\nfunction findBraceBlockEnd(lines: string[], startLine: number): number {\n let braceCount = 0;\n let foundOpen = false;\n \n for (let i = startLine; i < lines.length; i++) {\n const line = lines[i];\n \n for (const char of line) {\n if (char === '{') {\n braceCount++;\n foundOpen = true;\n } else if (char === '}') {\n braceCount--;\n }\n }\n \n if (foundOpen && braceCount === 0) {\n return i;\n }\n }\n \n // If no matching brace found, return a reasonable chunk\n return Math.min(startLine + 50, lines.length - 1);\n}\n\n/**\n * Find block end for Python (indentation-based)\n */\nfunction findPythonBlockEnd(lines: string[], startLine: number): number {\n const startIndent = getIndentLevel(lines[startLine]);\n \n for (let i = startLine + 1; i < lines.length; i++) {\n const line = lines[i];\n \n // Skip empty lines\n if (!line.trim()) {\n continue;\n }\n \n const indent = getIndentLevel(line);\n \n // Block ends when we return to same or lower indentation\n if (indent <= startIndent && line.trim()) {\n return i - 1;\n }\n }\n \n return lines.length - 1;\n}\n\n/**\n * Get indentation level of a line\n */\nfunction getIndentLevel(line: string): number {\n const match = line.match(/^(\\s*)/);\n return match ? match[1].length : 0;\n}\n\n/**\n * Merge small adjacent blocks\n */\nfunction mergeSmallBlocks(\n blocks: Array<{ startLine: number; endLine: number; type: string; name?: string }>,\n lines: string[]\n): Array<{ startLine: number; endLine: number; type: string; name?: string }> {\n if (blocks.length === 0) {\n return blocks;\n }\n\n const merged: typeof blocks = [];\n let current = blocks[0];\n\n for (let i = 1; i < blocks.length; i++) {\n const next = blocks[i];\n const currentContent = lines.slice(current.startLine, current.endLine + 1).join('\\n');\n const gap = next.startLine - current.endLine;\n \n // Merge if current block is small and gap is small\n if (currentContent.length < 500 && gap <= 3) {\n current = {\n startLine: current.startLine,\n endLine: next.endLine,\n type: 'block',\n name: current.name,\n };\n } else {\n merged.push(current);\n current = next;\n }\n }\n \n merged.push(current);\n return merged;\n}\n\n/**\n * Split a large block into smaller chunks\n */\nfunction splitLargeBlock(\n filePath: string,\n content: string,\n startLine: number,\n language: string,\n type: string,\n name?: string\n): Chunk[] {\n const chunks: Chunk[] = [];\n const lines = content.split('\\n');\n \n let currentStart = 0;\n let currentChunk = '';\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const newChunk = currentChunk + (currentChunk ? '\\n' : '') + line;\n \n if (newChunk.length > MAX_CHUNK_SIZE && currentChunk) {\n const contentHash = computeContentHash(currentChunk);\n chunks.push({\n id: generateChunkId(contentHash, chunks.length),\n text: buildChunkText(filePath, currentChunk, name),\n contentHash,\n chunkIndex: chunks.length,\n metadata: {\n filePath,\n startLine: startLine + currentStart + 1,\n endLine: startLine + i,\n language,\n chunkType: type as ChunkType,\n symbolName: name,\n },\n });\n \n currentStart = i;\n currentChunk = line;\n } else {\n currentChunk = newChunk;\n }\n }\n \n // Add remaining content\n if (currentChunk.trim()) {\n const contentHash = computeContentHash(currentChunk);\n chunks.push({\n id: generateChunkId(contentHash, chunks.length),\n text: buildChunkText(filePath, currentChunk, name),\n contentHash,\n chunkIndex: chunks.length,\n metadata: {\n filePath,\n startLine: startLine + currentStart + 1,\n endLine: startLine + lines.length,\n language,\n chunkType: type as ChunkType,\n symbolName: name,\n },\n });\n }\n\n return chunks;\n}\n\n/**\n * Sliding window chunking for non-code files\n */\nfunction chunkSlidingWindow(filePath: string, content: string, language: string): Chunk[] {\n const chunks: Chunk[] = [];\n \n // If content is small enough, return as single chunk\n if (content.length <= MAX_CHUNK_SIZE) {\n const contentHash = computeContentHash(content);\n chunks.push({\n id: generateChunkId(contentHash, 0),\n text: buildChunkText(filePath, content),\n contentHash,\n chunkIndex: 0,\n metadata: {\n filePath,\n startLine: 1,\n endLine: content.split('\\n').length,\n language,\n chunkType: 'sliding',\n },\n });\n return chunks;\n }\n\n // Split by lines to preserve line boundaries\n const lines = content.split('\\n');\n let currentStart = 0;\n let currentChunk = '';\n let currentLineStart = 0;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const newChunk = currentChunk + (currentChunk ? '\\n' : '') + line;\n \n if (newChunk.length >= SLIDING_WINDOW_SIZE) {\n const contentHash = computeContentHash(currentChunk || newChunk);\n chunks.push({\n id: generateChunkId(contentHash, chunks.length),\n text: buildChunkText(filePath, currentChunk || newChunk),\n contentHash,\n chunkIndex: chunks.length,\n metadata: {\n filePath,\n startLine: currentLineStart + 1,\n endLine: i + 1,\n language,\n chunkType: 'sliding',\n },\n });\n \n // Move back for overlap\n const overlapLines = Math.floor(SLIDING_WINDOW_OVERLAP / 50); // rough estimate\n currentLineStart = Math.max(currentStart, i - overlapLines);\n currentChunk = lines.slice(currentLineStart, i + 1).join('\\n');\n currentStart = currentLineStart;\n } else {\n currentChunk = newChunk;\n }\n }\n\n // Add remaining content\n if (currentChunk.trim() && currentChunk.length > 50) {\n const contentHash = computeContentHash(currentChunk);\n chunks.push({\n id: generateChunkId(contentHash, chunks.length),\n text: buildChunkText(filePath, currentChunk),\n contentHash,\n chunkIndex: chunks.length,\n metadata: {\n filePath,\n startLine: currentLineStart + 1,\n endLine: lines.length,\n language,\n chunkType: 'sliding',\n },\n });\n }\n\n return reindexChunks(chunks);\n}\n\n/**\n * Build the text to embed, including file context\n */\nfunction buildChunkText(filePath: string, content: string, symbolName?: string): string {\n const fileName = basename(filePath);\n let text = `File: ${filePath}\\n`;\n \n if (symbolName) {\n text += `Symbol: ${symbolName}\\n`;\n }\n \n text += `\\n${content}`;\n return text;\n}\n\n/**\n * Re-index chunks to ensure sequential chunk indices\n */\nfunction reindexChunks(chunks: Chunk[]): Chunk[] {\n return chunks.map((chunk, index) => ({\n ...chunk,\n chunkIndex: index,\n id: generateChunkId(chunk.contentHash, index),\n }));\n}\n","/**\n * Vector client - uses remote server API for vector operations\n * This removes the need for the private vector SDK in the client\n */\n\nimport { getConfig } from '../config/index.js';\n\n// Types for vector operations (matching remote server API)\nexport interface EmbeddingRequest {\n texts: Array<{ id: string; text: string; document: Record<string, unknown> }>;\n namespace: string;\n embeddingModel?: string;\n}\n\nexport interface EmbeddingError {\n id?: string;\n error: string;\n}\n\nexport interface EmbeddingResult {\n processedCount: number;\n failedCount: number;\n errors?: EmbeddingError[];\n}\n\nexport interface SearchRequest {\n query: string;\n namespace: string;\n topK?: number;\n embeddingModel?: string;\n}\n\nexport interface SearchMatch {\n id: string;\n score: number;\n metadata?: Record<string, unknown>;\n}\n\nexport interface SearchResult {\n matches: SearchMatch[];\n}\n\n// Remote vector client state\nlet remoteServerUrl: string | null = null;\nlet authKey: string | null = null;\n\n/**\n * Initialize the vector client with remote server config\n */\nexport function initVectorClient(serverUrl: string, key: string) {\n remoteServerUrl = serverUrl.replace(/\\/$/, '');\n authKey = key;\n}\n\n/**\n * Check if vector client is configured\n */\nexport function isVectorClientConfigured(): boolean {\n return !!remoteServerUrl && !!authKey;\n}\n\n/**\n * HTTP helper for remote vector API calls\n */\nasync function vectorApi<T>(\n path: string,\n options: { method?: string; body?: unknown } = {}\n): Promise<T> {\n if (!remoteServerUrl || !authKey) {\n throw new Error('Vector client not initialized - remote server not configured');\n }\n \n const url = `${remoteServerUrl}/vectors${path}`;\n const init: RequestInit = {\n method: options.method || 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${authKey}`,\n },\n };\n \n if (options.body) {\n init.body = JSON.stringify(options.body);\n }\n \n const response = await fetch(url, init);\n \n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n throw new Error(error.error || `HTTP ${response.status}`);\n }\n \n return response.json() as Promise<T>;\n}\n\n/**\n * Remote vector client that calls the remote server\n * Implements same interface as the old VectorClient SDK\n */\nexport const remoteVectorClient = {\n embeddings: {\n /**\n * Create embeddings and store in vector DB\n */\n async createAndWait(\n texts: Array<{ id: string; text: string; document: Record<string, unknown> }>,\n options: {\n namespace: string;\n embeddingModel?: string;\n }\n ): Promise<EmbeddingResult> {\n return vectorApi<EmbeddingResult>('/embed', {\n method: 'POST',\n body: {\n texts,\n namespace: options.namespace,\n embeddingModel: options.embeddingModel,\n },\n });\n },\n },\n \n search: {\n /**\n * Query vectors using semantic search\n */\n async queryAndWait(\n query: string,\n options: {\n namespace: string;\n topK?: number;\n includeMetadata?: boolean;\n embeddingModel?: string;\n }\n ): Promise<SearchResult> {\n return vectorApi<SearchResult>('/search', {\n method: 'POST',\n body: {\n query,\n namespace: options.namespace,\n topK: options.topK || 10,\n embeddingModel: options.embeddingModel,\n },\n });\n },\n },\n \n /**\n * Delete a namespace (if supported)\n */\n async deleteNamespace(namespace: string): Promise<void> {\n await vectorApi(`/namespace/${encodeURIComponent(namespace)}`, {\n method: 'DELETE',\n });\n },\n \n /**\n * Close client (no-op for HTTP client)\n */\n async close(): Promise<void> {\n // No-op - HTTP connections don't need cleanup\n },\n};\n\n// Type alias for the vector client\nexport type VectorClient = typeof remoteVectorClient;\n\n/**\n * Get the Vector client\n * Returns null if remote server is not configured\n */\nexport function getVectorClient(): VectorClient | null {\n if (!isVectorClientConfigured()) {\n // Try to initialize from config\n try {\n const config = getConfig();\n if (config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey) {\n initVectorClient(config.resolvedRemoteServer.url, config.resolvedRemoteServer.authKey);\n } else {\n return null;\n }\n } catch {\n return null;\n }\n }\n \n return remoteVectorClient;\n}\n\n/**\n * Close the vector client (no-op for HTTP client)\n */\nexport async function closeVectorClient(): Promise<void> {\n // No-op - HTTP connections don't need cleanup\n}\n\n/**\n * Check if Vector Gateway is configured (via remote server)\n */\nexport function isVectorGatewayConfigured(): boolean {\n try {\n const config = getConfig();\n return !!(config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey);\n } catch {\n return false;\n }\n}\n\n/**\n * Get the configured embedding model\n */\nexport function getEmbeddingModel(): string {\n try {\n const config = getConfig();\n return config.resolvedVectorGateway.embeddingModel;\n } catch {\n return 'gemini-embedding-001';\n }\n}\n","/**\n * Repository indexing pipeline\n * Walks the repo, chunks files, and sends to Vector Gateway for embedding\n */\n\nimport { readFileSync, statSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport { minimatch } from 'minimatch';\nimport { getConfig } from '../config/index.js';\nimport { getDb, indexedChunkQueries, indexStatusQueries } from '../db/index.js';\nimport { Chunk, IndexOptions, IndexProgress, IndexResult, IndexStatus } from './types.js';\nimport { getRepoNamespace, isGitRepository } from './namespace.js';\nimport { chunkFile } from './chunker.js';\nimport { getVectorClient, closeVectorClient, getEmbeddingModel } from './client.js';\n\n// Max file size to index (1MB)\nconst MAX_FILE_SIZE = 1024 * 1024;\n\n// Batch size and concurrency for embedding requests\nconst EMBEDDING_BATCH_SIZE = 50;\nconst EMBEDDING_CONCURRENCY = 5;\nconst EMBEDDING_RETRIES = 2;\n\nfunction parsePositiveInt(value: string | undefined, fallback: number): number {\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return fallback;\n }\n return Math.floor(parsed);\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) {\n return error.message || 'Unknown error';\n }\n if (typeof error === 'string') {\n return error;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return 'Unknown error';\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Check if a path matches any exclude pattern\n */\nfunction isPathExcluded(relativePath: string, exclude: string[]): boolean {\n return exclude.some((pattern) => {\n // Direct match\n if (minimatch(relativePath, pattern, { dot: true })) {\n return true;\n }\n // For directory patterns like \"node_modules/**\", also check if the path\n // starts with the directory name (for skipping entire directories)\n if (pattern.endsWith('/**')) {\n const dirPattern = pattern.slice(0, -3); // Remove \"/**\"\n if (relativePath === dirPattern || relativePath.startsWith(dirPattern + '/')) {\n return true;\n }\n }\n return false;\n });\n}\n\n/**\n * Walk directory and collect files matching patterns\n */\nasync function walkDirectory(\n dir: string,\n include: string[],\n exclude: string[],\n baseDir: string\n): Promise<string[]> {\n const { readdirSync } = await import('node:fs');\n const { join, relative } = await import('node:path');\n \n const files: string[] = [];\n \n function walk(currentDir: string) {\n let entries;\n try {\n entries = readdirSync(currentDir, { withFileTypes: true });\n } catch {\n // Skip directories we can't read\n return;\n }\n \n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n const relativePath = relative(baseDir, fullPath);\n \n // Check exclusions first\n if (isPathExcluded(relativePath, exclude)) {\n continue;\n }\n \n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile()) {\n // Check inclusions\n const isIncluded = include.some((pattern) => {\n return minimatch(relativePath, pattern, { dot: true });\n });\n \n if (isIncluded) {\n files.push(fullPath);\n }\n }\n }\n }\n \n walk(dir);\n return files;\n}\n\n/**\n * Check if a file should be skipped (binary, too large, etc.)\n */\nfunction shouldSkipFile(filePath: string): { skip: boolean; reason?: string } {\n try {\n const stats = statSync(filePath);\n \n if (stats.size > MAX_FILE_SIZE) {\n return { skip: true, reason: 'File too large (>1MB)' };\n }\n \n if (stats.size === 0) {\n return { skip: true, reason: 'Empty file' };\n }\n \n // For text detection, just try to read the file as UTF-8\n // If it fails or has null bytes, it's likely binary\n try {\n const content = readFileSync(filePath, 'utf-8');\n // Check for null bytes in first 1000 chars (binary indicator)\n const sample = content.slice(0, 1000);\n if (sample.includes('\\0')) {\n return { skip: true, reason: 'Binary file' };\n }\n } catch {\n return { skip: true, reason: 'Cannot read as text' };\n }\n \n return { skip: false };\n } catch (error) {\n return { skip: true, reason: `Error reading file: ${error}` };\n }\n}\n\n/**\n * Index a repository for semantic search\n */\nexport async function indexRepository(options: IndexOptions): Promise<IndexResult> {\n const startTime = Date.now();\n const errors: Array<{ file: string; error: string }> = [];\n \n const progress: IndexProgress = {\n phase: 'scanning',\n totalFiles: 0,\n processedFiles: 0,\n totalChunks: 0,\n newChunks: 0,\n skippedChunks: 0,\n };\n \n const reportProgress = () => {\n if (options.onProgress) {\n options.onProgress({ ...progress });\n }\n };\n\n // Check if git repository\n if (!isGitRepository(options.workingDirectory)) {\n return {\n success: false,\n namespace: '',\n totalFiles: 0,\n totalChunks: 0,\n newChunks: 0,\n skippedChunks: 0,\n failedChunks: 0,\n duration: Date.now() - startTime,\n errors: [{ file: '', error: 'Not a git repository' }],\n };\n }\n\n // Get config\n const config = getConfig();\n const { include, exclude, namespace: configNamespace } = config.resolvedVectorGateway;\n\n // Get namespace\n const namespace = await getRepoNamespace(options.workingDirectory, configNamespace);\n if (!namespace) {\n return {\n success: false,\n namespace: '',\n totalFiles: 0,\n totalChunks: 0,\n newChunks: 0,\n skippedChunks: 0,\n failedChunks: 0,\n duration: Date.now() - startTime,\n errors: [{ file: '', error: 'Could not determine repository namespace. Ensure git remote is configured.' }],\n };\n }\n\n // Get vector client\n const client = getVectorClient();\n if (!client) {\n return {\n success: false,\n namespace,\n totalFiles: 0,\n totalChunks: 0,\n newChunks: 0,\n skippedChunks: 0,\n failedChunks: 0,\n duration: Date.now() - startTime,\n errors: [{ file: '', error: 'Remote server not configured. Set SPARKECODER_REMOTE_URL/SPARKECODER_AUTH_KEY or remoteServer in sparkecoder.config.json' }],\n };\n }\n\n try {\n // Phase 1: Scan files\n progress.phase = 'scanning';\n reportProgress();\n \n const files = await walkDirectory(\n options.workingDirectory,\n include,\n exclude,\n options.workingDirectory\n );\n \n progress.totalFiles = files.length;\n reportProgress();\n\n // Phase 2: Chunk files\n progress.phase = 'chunking';\n reportProgress();\n \n const allChunks: Chunk[] = [];\n \n for (const filePath of files) {\n const relativePath = relative(options.workingDirectory, filePath);\n progress.currentFile = relativePath;\n \n const skipCheck = shouldSkipFile(filePath);\n if (skipCheck.skip) {\n if (options.verbose) {\n console.log(`Skipping ${relativePath}: ${skipCheck.reason}`);\n }\n progress.processedFiles++;\n reportProgress();\n continue;\n }\n \n try {\n const content = readFileSync(filePath, 'utf-8');\n const chunks = chunkFile(relativePath, content);\n allChunks.push(...chunks);\n progress.totalChunks += chunks.length;\n } catch (error) {\n errors.push({ file: relativePath, error: String(error) });\n }\n \n progress.processedFiles++;\n reportProgress();\n }\n\n // Phase 3: Check existing hashes\n progress.phase = 'checking';\n reportProgress();\n \n const db = getDb();\n const existingHashes = new Set<string>();\n \n if (!options.force) {\n // Get all existing chunk IDs for this namespace\n const existingChunks = await indexedChunkQueries.getByNamespace(db, namespace);\n for (const chunk of existingChunks) {\n existingHashes.add(chunk.id);\n }\n }\n \n // Filter to new chunks only\n const newChunks = allChunks.filter((chunk) => !existingHashes.has(chunk.id));\n progress.newChunks = newChunks.length;\n progress.skippedChunks = allChunks.length - newChunks.length;\n reportProgress();\n\n // Phase 4: Embed new chunks\n progress.phase = 'embedding';\n reportProgress();\n \n const embeddingModel = getEmbeddingModel();\n let failedChunks = 0;\n \n // Process in batches (parallelized with a worker pool)\n const batchSize = parsePositiveInt(process.env.SPARKECODER_INDEX_BATCH_SIZE, EMBEDDING_BATCH_SIZE);\n const concurrency = parsePositiveInt(process.env.SPARKECODER_INDEX_CONCURRENCY, EMBEDDING_CONCURRENCY);\n const maxRetries = parsePositiveInt(process.env.SPARKECODER_INDEX_RETRIES, EMBEDDING_RETRIES);\n const totalBatches = Math.ceil(newChunks.length / batchSize);\n console.log(\n `[indexer] Starting embedding: ${newChunks.length} chunks in ${totalBatches} batches (batchSize=${batchSize}, concurrency=${concurrency}, retries=${maxRetries})`\n );\n \n const batches = newChunks.reduce<Array<{ batchNum: number; batch: Chunk[] }>>((acc, chunk, index) => {\n if (index % batchSize === 0) {\n acc.push({\n batchNum: Math.floor(index / batchSize) + 1,\n batch: newChunks.slice(index, index + batchSize),\n });\n }\n return acc;\n }, []);\n\n const processBatch = async (batchNum: number, batch: Chunk[]) => {\n console.log(`[indexer] Batch ${batchNum}/${totalBatches}: embedding ${batch.length} chunks...`);\n const texts = batch.map((chunk) => ({\n id: chunk.id,\n text: chunk.text,\n document: {\n filePath: chunk.metadata.filePath,\n startLine: chunk.metadata.startLine,\n endLine: chunk.metadata.endLine,\n language: chunk.metadata.language,\n chunkType: chunk.metadata.chunkType,\n symbolName: chunk.metadata.symbolName,\n contentHash: chunk.contentHash,\n },\n }));\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const batchStartTime = Date.now();\n try {\n const result = await client.embeddings.createAndWait(texts, {\n namespace,\n embeddingModel,\n });\n \n const embedTime = Date.now() - batchStartTime;\n console.log(`[indexer] Batch ${batchNum}: embed completed in ${embedTime}ms - processed: ${result.processedCount}, failed: ${result.failedCount}`);\n \n failedChunks += result.failedCount;\n \n // Record successful chunks in local DB (batch upsert)\n const successfulChunks = batch.filter(\n (chunk) => !result.errors?.find((e) => e.id === chunk.id)\n );\n \n if (successfulChunks.length > 0) {\n console.log(`[indexer] Batch ${batchNum}: recording ${successfulChunks.length} chunks to DB...`);\n const dbStartTime = Date.now();\n \n await indexedChunkQueries.batchUpsert(db, successfulChunks.map((chunk) => ({\n id: chunk.id,\n contentHash: chunk.contentHash,\n filePath: chunk.metadata.filePath,\n repoNamespace: namespace,\n startLine: chunk.metadata.startLine,\n endLine: chunk.metadata.endLine,\n language: chunk.metadata.language,\n })));\n \n const dbTime = Date.now() - dbStartTime;\n console.log(`[indexer] Batch ${batchNum}: DB batch upsert completed in ${dbTime}ms`);\n }\n \n if (result.errors?.length) {\n for (const err of result.errors) {\n const chunk = batch.find((c) => c.id === err.id);\n if (chunk) {\n errors.push({ file: chunk.metadata.filePath, error: err.error });\n }\n }\n } else if (result.failedCount > 0) {\n errors.push({ file: `batch ${batchNum}`, error: `Embedding failed for ${result.failedCount} chunks (no error details returned)` });\n }\n \n return;\n } catch (error) {\n const errorMsg = formatError(error);\n console.error(`[indexer] Batch ${batchNum}: ERROR (attempt ${attempt + 1}/${maxRetries + 1}) - ${errorMsg}`);\n if (attempt >= maxRetries) {\n failedChunks += batch.length;\n errors.push({ file: `batch ${batchNum}`, error: errorMsg });\n return;\n }\n await sleep(500 * (attempt + 1));\n } finally {\n reportProgress();\n }\n }\n };\n\n let nextBatchIndex = 0;\n const workerCount = Math.min(concurrency, batches.length);\n const workers = Array.from({ length: workerCount }, async () => {\n while (nextBatchIndex < batches.length) {\n const currentIndex = nextBatchIndex;\n nextBatchIndex += 1;\n const { batchNum, batch } = batches[currentIndex];\n await processBatch(batchNum, batch);\n }\n });\n\n await Promise.all(workers);\n \n console.log(`[indexer] Embedding complete. Updating index status...`);\n\n // Update index status\n try {\n console.log(`[indexer] Calling indexStatusQueries.upsert with totalChunks: ${allChunks.length}`);\n await indexStatusQueries.upsert(db, {\n id: namespace,\n repoNamespace: namespace,\n totalChunks: allChunks.length,\n lastFullIndex: options.force ? new Date() : undefined,\n lastIncrementalIndex: new Date(),\n });\n console.log(`[indexer] Index status updated successfully`);\n } catch (statusError) {\n console.error(`[indexer] Failed to update index status:`, statusError);\n throw statusError;\n }\n\n // Phase 5: Done\n progress.phase = 'done';\n reportProgress();\n\n return {\n success: true,\n namespace,\n totalFiles: files.length,\n totalChunks: allChunks.length,\n newChunks: newChunks.length - failedChunks,\n skippedChunks: progress.skippedChunks,\n failedChunks,\n duration: Date.now() - startTime,\n errors,\n };\n } finally {\n await closeVectorClient();\n }\n}\n\n/**\n * Get the index status for a repository\n */\nexport async function getIndexStatus(workingDirectory: string): Promise<IndexStatus> {\n const config = getConfig();\n const namespace = await getRepoNamespace(\n workingDirectory,\n config.resolvedVectorGateway.namespace\n );\n \n const isConfigured = config.resolvedRemoteServer.isConfigured;\n \n if (!namespace) {\n return {\n namespace: '',\n totalChunks: 0,\n lastFullIndex: null,\n lastIncrementalIndex: null,\n isConfigured,\n };\n }\n \n try {\n const db = getDb();\n const status = await indexStatusQueries.get(db, namespace);\n \n if (!status) {\n return {\n namespace,\n totalChunks: 0,\n lastFullIndex: null,\n lastIncrementalIndex: null,\n isConfigured,\n };\n }\n \n return {\n namespace,\n totalChunks: status.totalChunks ?? 0,\n lastFullIndex: status.lastFullIndex ?? null,\n lastIncrementalIndex: status.lastIncrementalIndex ?? null,\n isConfigured,\n };\n } catch {\n return {\n namespace,\n totalChunks: 0,\n lastFullIndex: null,\n lastIncrementalIndex: null,\n isConfigured,\n };\n }\n}\n\n/**\n * Check if an index exists for a repository\n */\nexport async function checkIndexExists(workingDirectory: string): Promise<boolean> {\n const status = await getIndexStatus(workingDirectory);\n return status.totalChunks > 0;\n}\n","import { ToolSet } from 'ai';\nimport { createBashTool, BashToolOptions, BashToolProgress } from './bash.js';\nimport { createReadFileTool, ReadFileToolOptions } from './read-file.js';\nimport { createWriteFileTool, WriteFileToolOptions, WriteFileProgress } from './write-file.js';\nimport { createTodoTool, TodoToolOptions } from './todo.js';\nimport { createLoadSkillTool, LoadSkillToolOptions } from './load-skill.js';\nimport { createLinterTool, LinterToolOptions } from './linter.js';\nimport { createSearchTool, SearchToolOptions, SearchToolProgress } from './search.js';\nimport { createSemanticSearchTool, SemanticSearchToolOptions, SemanticSearchResult } from './semantic-search.js';\nimport { isVectorGatewayConfigured, checkIndexExists } from '../semantic/index.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 /** Called when write_file has progress to report (for streaming content) */\n onWriteFileProgress?: (progress: WriteFileProgress) => void;\n /** Called when explore_agent tool has progress to report (subagent steps) */\n onSearchProgress?: (progress: SearchToolProgress) => void;\n /** Enable LSP diagnostics for file edits (default: true) */\n enableLSP?: boolean;\n /** Enable semantic search if configured (default: true) */\n enableSemanticSearch?: boolean;\n}\n\n/**\n * Create all tools for an agent session\n * Note: This is now async to support checking semantic search availability\n */\nexport async function createTools(options: CreateToolsOptions): Promise<ToolSet> {\n const tools: ToolSet = {\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 enableLSP: options.enableLSP ?? true,\n onProgress: options.onWriteFileProgress,\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 linter: createLinterTool({\n workingDirectory: options.workingDirectory,\n }),\n\n explore_agent: createSearchTool({\n sessionId: options.sessionId,\n workingDirectory: options.workingDirectory,\n onProgress: options.onSearchProgress,\n }),\n };\n\n // Conditionally add semantic_search if configured and index exists\n if (options.enableSemanticSearch !== false) {\n try {\n if (isVectorGatewayConfigured()) {\n const hasIndex = await checkIndexExists(options.workingDirectory);\n if (hasIndex) {\n tools.semantic_search = createSemanticSearchTool({\n workingDirectory: options.workingDirectory,\n });\n }\n }\n } catch {\n // Silently skip semantic search if there are any issues\n }\n }\n\n return tools;\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';\nexport { createLinterTool } from './linter.js';\nexport { createSearchTool } from './search.js';\nexport { createSemanticSearchTool } from './semantic-search.js';\n\n// Export types\nexport type { BashToolOptions, BashToolProgress } from './bash.js';\nexport type { ReadFileToolOptions } from './read-file.js';\nexport type { WriteFileToolOptions, WriteFileProgress } from './write-file.js';\nexport type { TodoToolOptions } from './todo.js';\nexport type { LoadSkillToolOptions } from './load-skill.js';\nexport type { LinterToolOptions } from './linter.js';\nexport type { SearchToolOptions, SearchToolProgress } from './search.js';\nexport type { SemanticSearchToolOptions, SemanticSearchResult } from './semantic-search.js';","import { generateText, type ModelMessage as AIModelMessage } from 'ai';\nimport { resolveModel } from './model.js';\nimport { messageQueries, ModelMessage } from '../db/index.js';\nimport { calculateContextSize } from '../utils/truncate.js';\nimport { createSummaryPrompt } from './prompts.js';\nimport { getConfig } from '../config/index.js';\nimport { sanitizeModelMessages } from '../utils/sanitize-messages.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 * Includes self-repair: if messages from the database have been corrupted\n * (e.g., Date objects in tool outputs from parseDates), they are automatically\n * sanitized to conform to the AI SDK's ModelMessage schema.\n */\n async getMessages(): Promise<AIModelMessage[]> {\n let modelMessages = (await messageQueries.getModelMessages(this.sessionId)) as AIModelMessage[];\n\n // Sanitize messages to ensure they conform to AI SDK schema.\n // This catches and repairs corruption from the database layer\n // (e.g., parseDates converting date strings to Date objects in tool outputs).\n modelMessages = sanitizeModelMessages(modelMessages) 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: resolveModel(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 * Content can be a string or an array of content parts (for messages with images/files)\n */\n async addUserMessage(content: string | Array<{ type: string; text?: string; image?: string; data?: string; mediaType?: string }>): Promise<void> {\n const userMessage: ModelMessage = {\n role: 'user',\n content: content as any,\n };\n await 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 async addResponseMessages(messages: AIModelMessage[]): Promise<void> {\n await messageQueries.addMany(this.sessionId, messages as ModelMessage[]);\n }\n\n /**\n * Get current context statistics\n */\n async getStats(): Promise<{ messageCount: number; contextChars: number; hasSummary: boolean }> {\n const messages = (await 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 async clear(): Promise<void> {\n await messageQueries.deleteBySession(this.sessionId);\n this.summary = null;\n }\n}\n","import os from 'node:os';\nimport {\n loadAllSkillsFromDiscovered,\n getGlobMatchedSkills,\n loadAgentsMd,\n formatSkillsForContext,\n formatAlwaysLoadedSkills,\n formatGlobMatchedSkills,\n formatAgentsMdContent,\n} from '../skills/index.js';\nimport { todoQueries, TodoItem } from '../db/index.js';\nimport { DiscoveredSkills } from '../config/types.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 discoveredSkills?: DiscoveredSkills;\n activeFiles?: string[];\n customInstructions?: string;\n}): Promise<string> {\n const {\n workingDirectory,\n skillsDirectories,\n sessionId,\n discoveredSkills,\n activeFiles = [],\n customInstructions,\n } = options;\n\n // Load skills using the enhanced system if discoveredSkills is provided\n let alwaysLoadedContent = '';\n let globMatchedContent = '';\n let agentsMdContent = '';\n let onDemandSkillsContext = '';\n\n if (discoveredSkills) {\n // Use the new enhanced skill loading\n const { always, onDemand, all } = await loadAllSkillsFromDiscovered(discoveredSkills);\n\n // Format always-loaded skills\n alwaysLoadedContent = formatAlwaysLoadedSkills(always);\n\n // Format on-demand skills for context\n onDemandSkillsContext = formatSkillsForContext(onDemand);\n\n // Load AGENTS.md if present\n const agentsMd = await loadAgentsMd(discoveredSkills.agentsMdPath);\n agentsMdContent = formatAgentsMdContent(agentsMd);\n\n // Load glob-matched skills based on active files\n if (activeFiles.length > 0) {\n const globMatched = await getGlobMatchedSkills(all, activeFiles, workingDirectory);\n globMatchedContent = formatGlobMatchedSkills(globMatched);\n }\n } else {\n // Legacy fallback: just load skills from directories\n const { loadAllSkills } = await import('../skills/index.js');\n const skills = await loadAllSkills(skillsDirectories);\n onDemandSkillsContext = formatSkillsForContext(skills);\n }\n\n // Load current todos\n const todos = await 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- **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)\n- **todo**: Manage your task list to track progress on complex operations\n- **load_skill**: Load specialized knowledge documents for specific tasks\n- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning\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\nTerminal output is stored in the global SparkECoder data directory. Use the \\`tail\\` option to read recent output.\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- After making changes, use the \\`linter\\` tool to check for type errors and lint issues\n- The \\`write_file\\` tool automatically shows lint errors in its output for TypeScript/JavaScript files\n- If the user asks to write/create a file, always use \\`write_file\\` rather than printing the full contents\n- If the user requests a file but does not provide a path, choose a sensible default (e.g. \\`index.html\\`) and proceed\n- For large content (hundreds of lines), avoid placing it in chat output; write to a file instead\n\n### Linter Tool\nThe linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:\n\\`\\`\\`\nlinter({}) // Check all recently edited files\nlinter({ paths: [\"src/app.ts\"] }) // Check specific files\nlinter({ paths: [\"src/\"] }) // Check all files in a directory\n\\`\\`\\`\nUse this proactively after making code changes to catch errors early.\n\n### Searching and Exploration\n\n**Choose the right search approach:**\n\n1. **Use the \\`explore_agent\\` tool (Explore agent)** for:\n - Semantic/exploratory questions: \"How does authentication work?\", \"Where is user data processed?\"\n - Finding code by meaning or concept, not exact text\n - Understanding how features are implemented across multiple files\n - Exploring unfamiliar parts of the codebase\n - Questions like \"where\", \"how\", \"what does X do\"\n \n The Explore agent is a mini-agent that intelligently explores the codebase, reads relevant files, and returns a summary of what it found. It's best for understanding and discovery.\n\n2. **Use direct commands (grep/rg, find)** for:\n - Exact string matches: \\`rg \"functionName\"\\`, \\`rg \"class MyClass\"\\`\n - Finding files by name: \\`find . -name \"*.config.ts\"\\`\n - Simple pattern matching when you know exactly what you're looking for\n - Counting occurrences or listing all matches\n\n**Examples:**\n- \"Where is the API authentication handled?\" → Use \\`explore_agent\\` tool\n- \"Find all usages of getUserById\" → Use \\`rg \"getUserById\"\\`\n- \"How does the payment flow work?\" → Use \\`explore_agent\\` tool\n- \"Find files named config\" → Use \\`find . -name \"*config*\"\\`\n\n${searchInstructions}\n\n###Follow 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### Follow these rules to be a good agent for the user:\n\n1. Understand first - Read relevant files before making any changes. Use the \\`explore_agent\\` tool for exploratory questions about how things work, and direct searches (grep/rg) for finding exact strings or file names.\n2. Plan for complexity - If the task involves 3+ steps or has meaningful trade-offs, create a todo list to track progress before implementing.\n3. Use the right tools - Have specialized tools for reading files, editing code, semantic search via subagents, and running terminal commands. Prefer these over raw shell commands.\n4. Work efficiently - When need to do multiple independent things (like reading several files), do them in parallel rather than one at a time.\n5. Be direct - Focus on technical accuracy rather than validation. If see issues with an approach or need clarification, say so.\n6. Verify my work - After making changes, check for linter errors and fix any introduced.\n7. Respect boundaries - Only commit code when explicitly asked, avoid creating unnecessary files, and don't make assumptions about things uncertain about.\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${agentsMdContent}\n\n${alwaysLoadedContent}\n\n${globMatchedContent}\n\n## On-Demand Skills\n${onDemandSkillsContext}\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","/**\n * Message sanitization for AI SDK ModelMessage compatibility.\n * \n * Ensures messages retrieved from the database conform to the AI SDK's\n * ModelMessage[] schema before being passed to streamText()/generateText().\n * \n * Handles two classes of issues:\n * \n * 1. SCHEMA CORRUPTION: The remote database client's `parseDates()` function\n * recursively converts ISO date strings (like `createdAt`) inside tool result\n * outputs to JavaScript Date objects. The AI SDK's jsonValueSchema only accepts\n * JSON primitives — Date objects are rejected, causing AI_InvalidPromptError.\n * \n * 2. CONSECUTIVE SAME-ROLE MESSAGES: If multiple user messages are saved to the\n * database without an assistant response between them (e.g., user sends two\n * messages quickly, or the previous stream errored before producing a response),\n * the Anthropic API rejects consecutive same-role messages. This module merges\n * them into a single message.\n * \n * This module provides a safety net that catches and repairs any corrupted\n * messages so the agent can self-heal even if the database layer returns\n * unexpected data.\n */\n\nimport { modelMessageSchema, type ModelMessage } from 'ai';\n\n/**\n * Recursively convert Date objects to ISO strings within a value.\n * This reverses any accidental Date conversions from parseDates.\n */\nfunction convertDatesToStrings(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.map(convertDatesToStrings);\n if (typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n result[k] = convertDatesToStrings(v);\n }\n return result;\n }\n return value;\n}\n\n/**\n * Sanitize a single ModelMessage so it passes AI SDK schema validation.\n * \n * Fixes known corruption patterns:\n * - Date objects inside tool result output (from parseDates)\n * - Date objects inside tool call input\n * - Date objects inside user/assistant content parts\n */\nfunction sanitizeMessage(msg: unknown): unknown {\n if (msg === null || msg === undefined || typeof msg !== 'object') return msg;\n \n const message = msg as Record<string, unknown>;\n \n // Only process messages with a recognized role\n if (!message.role || typeof message.role !== 'string') return msg;\n \n // Deep-convert any Date objects back to ISO strings\n return convertDatesToStrings(message);\n}\n\n/**\n * Extract the text content from a user message's content field.\n * Handles both string content and array-of-parts content.\n */\nfunction extractUserText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .filter((p: any) => p.type === 'text' && typeof p.text === 'string')\n .map((p: any) => p.text)\n .join('\\n');\n }\n return '';\n}\n\n/**\n * Merge the content of two user messages into a single content value.\n * \n * Rules:\n * - string + string → joined with \"\\n\\n\"\n * - array + array → concatenated (deduplicating text parts)\n * - string + array → string converted to text part, then concatenated\n * - array + string → string converted to text part, then appended\n */\nfunction mergeUserContent(\n a: unknown,\n b: unknown,\n): string | Array<Record<string, unknown>> {\n const aIsString = typeof a === 'string';\n const bIsString = typeof b === 'string';\n\n // Both strings: simple join\n if (aIsString && bIsString) {\n return `${a}\\n\\n${b}`;\n }\n\n // Normalise both to arrays\n const aParts: Array<Record<string, unknown>> = aIsString\n ? [{ type: 'text', text: a }]\n : Array.isArray(a)\n ? (a as Array<Record<string, unknown>>)\n : [];\n\n const bParts: Array<Record<string, unknown>> = bIsString\n ? [{ type: 'text', text: b }]\n : Array.isArray(b)\n ? (b as Array<Record<string, unknown>>)\n : [];\n\n return [...aParts, ...bParts];\n}\n\n/**\n * Merge consecutive same-role messages in the array.\n * \n * The Anthropic API (and some other providers) reject message arrays where\n * two consecutive messages share the same role. This can happen when:\n * - The user sends two messages before the agent responds\n * - The previous agent stream errored/aborted before producing a response\n * - Network issues cause duplicate message saves\n * \n * For user messages: merges content (text joined with newlines, parts concatenated)\n * For assistant messages: merges content parts into a single array\n * For tool messages: concatenates content arrays\n */\nfunction mergeConsecutiveSameRole(messages: ModelMessage[]): ModelMessage[] {\n if (messages.length <= 1) return messages;\n\n const merged: ModelMessage[] = [];\n\n for (const msg of messages) {\n const prev = merged[merged.length - 1];\n\n if (!prev || (prev as any).role !== (msg as any).role) {\n // Different role or first message — keep as-is\n merged.push(msg);\n continue;\n }\n\n // Same role as previous — merge\n const role = (msg as any).role as string;\n\n if (role === 'user') {\n const mergedContent = mergeUserContent((prev as any).content, (msg as any).content);\n merged[merged.length - 1] = { role: 'user', content: mergedContent } as any as ModelMessage;\n console.warn('[sanitize-messages] Merged consecutive user messages');\n } else if (role === 'assistant') {\n // Normalise both to arrays and concatenate\n const prevParts = typeof (prev as any).content === 'string'\n ? [{ type: 'text', text: (prev as any).content }]\n : Array.isArray((prev as any).content)\n ? (prev as any).content\n : [];\n const curParts = typeof (msg as any).content === 'string'\n ? [{ type: 'text', text: (msg as any).content }]\n : Array.isArray((msg as any).content)\n ? (msg as any).content\n : [];\n merged[merged.length - 1] = { role: 'assistant', content: [...prevParts, ...curParts] } as any as ModelMessage;\n console.warn('[sanitize-messages] Merged consecutive assistant messages');\n } else if (role === 'tool') {\n // Tool messages always have array content — concatenate\n const prevContent = Array.isArray((prev as any).content) ? (prev as any).content : [];\n const curContent = Array.isArray((msg as any).content) ? (msg as any).content : [];\n merged[merged.length - 1] = { role: 'tool', content: [...prevContent, ...curContent] } as any as ModelMessage;\n console.warn('[sanitize-messages] Merged consecutive tool messages');\n } else {\n // Unknown role — just push, don't try to merge\n merged.push(msg);\n }\n }\n\n return merged;\n}\n\n/**\n * Validate and sanitize an array of ModelMessage objects.\n * \n * Performs two passes:\n * 1. Schema repair — fixes individual messages that fail AI SDK validation\n * (Date objects, missing type wrappers, etc.)\n * 2. Sequence repair — merges consecutive same-role messages that providers\n * like Anthropic would reject\n * \n * Returns the original messages if they're all valid and properly sequenced,\n * or repaired copies if any had issues. Logs warnings for any messages that\n * needed repair so the issue is visible in server logs.\n */\nexport function sanitizeModelMessages(messages: ModelMessage[]): ModelMessage[] {\n // === Pass 1: Schema repair ===\n // Fast path: try validating the whole array first\n let allValid = true;\n for (const msg of messages) {\n try {\n modelMessageSchema.parse(msg);\n } catch {\n allValid = false;\n break;\n }\n }\n \n let result: ModelMessage[];\n \n if (allValid) {\n result = messages;\n } else {\n // Slow path: sanitize each message individually\n console.warn('[sanitize-messages] Detected invalid messages, attempting self-repair...');\n \n const sanitized: ModelMessage[] = [];\n let repairCount = 0;\n \n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n \n // Check if this specific message is valid\n try {\n modelMessageSchema.parse(msg);\n sanitized.push(msg);\n continue;\n } catch {\n // Needs repair\n }\n \n // Strategy 1: Convert Date objects to ISO strings\n const fixed = sanitizeMessage(msg) as ModelMessage;\n try {\n modelMessageSchema.parse(fixed);\n sanitized.push(fixed);\n repairCount++;\n console.warn(`[sanitize-messages] Repaired message ${i} (role=${(msg as any).role}) - converted Date objects to strings`);\n continue;\n } catch {\n // Strategy 1 failed\n }\n \n // Strategy 2: For tool messages, try wrapping raw output in { type: 'json', value: ... }\n // This handles cases where tool output was stored in legacy format\n if ((msg as any).role === 'tool' && Array.isArray((msg as any).content)) {\n const fixedContent = ((msg as any).content as any[]).map((part: any) => {\n if (part.type === 'tool-result' && part.output !== undefined) {\n const output = convertDatesToStrings(part.output);\n // If output doesn't have a recognized type discriminator, wrap it\n if (output && typeof output === 'object' && !(output as any).type) {\n return { ...part, output: { type: 'json', value: output } };\n }\n // If output has a type but it's not a recognized discriminator, wrap it\n const knownTypes = ['text', 'json', 'execution-denied', 'error-text', 'error-json', 'content'];\n if (output && typeof output === 'object' && !knownTypes.includes((output as any).type)) {\n return { ...part, output: { type: 'json', value: output } };\n }\n return { ...part, output };\n }\n return convertDatesToStrings(part);\n });\n \n const wrappedMsg = { ...(msg as any), content: fixedContent } as ModelMessage;\n try {\n modelMessageSchema.parse(wrappedMsg);\n sanitized.push(wrappedMsg);\n repairCount++;\n console.warn(`[sanitize-messages] Repaired message ${i} (role=tool) - wrapped raw output in json type`);\n continue;\n } catch {\n // Strategy 2 failed\n }\n }\n \n // Strategy 3: Last resort - include the message as-is and let it fail\n // downstream with a better error context. This is better than silently\n // dropping messages which could corrupt conversation state.\n console.error(\n `[sanitize-messages] Could not repair message ${i} (role=${(msg as any).role}). ` +\n `Message will be included as-is. Content keys: ${JSON.stringify(Object.keys(msg as any))}`,\n );\n sanitized.push(msg);\n }\n \n if (repairCount > 0) {\n console.warn(`[sanitize-messages] Self-repair complete: fixed ${repairCount}/${messages.length} messages`);\n }\n \n result = sanitized;\n }\n\n // === Pass 2: Sequence repair ===\n // Merge consecutive same-role messages (Anthropic rejects these)\n result = mergeConsecutiveSameRole(result);\n\n return result;\n}\n"],"mappings":"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","names":["readFile","resolve","extname","relative","existsSync","streamText","generateText","tool","stepCountIs","z","nanoid","z","exec","promisify","existsSync","mkdirSync","join","existsSync","mkdirSync","join","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","extname","dirname","existsSync","resolve","dirname","exec","promisify","execAsync","readFile","existsSync","resolve","extname","dirname","extname","z","tool","isAbsolute","resolve","relative","existsSync","dirname","mkdir","writeFile","readFile","tool","z","z","tool","tool","z","z","tool","tool","z","resolve","relative","isAbsolute","extname","existsSync","readdir","stat","z","readdir","resolve","extname","tool","isAbsolute","existsSync","stat","relative","tool","z","nanoid","nanoid","resolve","tool","z","exec","promisify","readFile","stat","readdir","resolve","relative","isAbsolute","existsSync","execAsync","promisify","exec","MAX_OUTPUT_CHARS","MAX_FILE_SIZE","tool","z","resolve","isAbsolute","existsSync","stat","readFile","relative","readdir","tool","z","tool","z","existsSync","readFileSync","join","minimatch","extname","basename","remoteServerUrl","authKey","readFileSync","relative","minimatch","MAX_FILE_SIZE","z","tool","minimatch","join","existsSync","readFileSync","generateText","platform","loadAllSkills","generateText","streamText","stepCountIs","generateText","tool","z","nanoid","resolve","exec"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config/types.ts","../../src/skills/index.ts","../../src/agent/index.ts","../../src/agent/model.ts","../../src/db/remote.ts","../../src/db/index.ts","../../src/config/index.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/lsp/index.ts","../../src/lsp/servers.ts","../../src/lsp/client.ts","../../src/lsp/types.ts","../../src/tools/todo.ts","../../src/tools/load-skill.ts","../../src/tools/linter.ts","../../src/tools/search.ts","../../src/agent/subagent.ts","../../src/agent/subagents/search.ts","../../src/tools/semantic-search.ts","../../src/semantic/namespace.ts","../../src/semantic/hasher.ts","../../src/semantic/chunker.ts","../../src/semantic/client.ts","../../src/semantic/indexer.ts","../../src/tools/index.ts","../../src/agent/context.ts","../../src/agent/prompts.ts","../../src/utils/sanitize-messages.ts"],"sourcesContent":["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 // Whether to always inject this skill into context (vs on-demand loading)\n alwaysApply: z.boolean().optional().default(false),\n // Glob patterns - auto-inject when working with matching files\n globs: z.array(z.string()).optional().default([]),\n});\n\n// Skill loading type\nexport type SkillLoadType = 'always' | 'on_demand' | 'glob_matched';\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// Vector Gateway configuration for semantic search\nexport const VectorGatewayConfigSchema = z\n .object({\n // Redis URL for Vector Gateway (or use VECTOR_REDIS_URL env var)\n redisUrl: z.string().optional(),\n // HTTP URL for database operations (or use VECTOR_HTTP_URL env var)\n httpUrl: z.string().optional(),\n // Embedding model to use (default: text-embedding-3-small)\n embeddingModel: z.string().default('gemini-embedding-001'),\n // Custom namespace override (auto-generated from git remote if not set)\n namespace: z.string().optional(),\n // File patterns to include in indexing\n include: z\n .array(z.string())\n .optional()\n .default([\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.py',\n '**/*.go',\n '**/*.rs',\n '**/*.java',\n '**/*.md',\n '**/*.mdx',\n '**/*.txt',\n ]),\n // File patterns to exclude from indexing\n exclude: z\n .array(z.string())\n .optional()\n .default([\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/.git/**',\n '**/.next/**',\n '**/*.min.js',\n '**/*.bundle.js',\n '**/pnpm-lock.yaml',\n '**/package-lock.json',\n '**/yarn.lock',\n '**/.test-workspace/**',\n '**/.semantic-test-workspace/**',\n '**/.semantic-integration-test/**',\n ]),\n })\n .optional();\n\n// Remote server configuration\nexport const RemoteServerConfigSchema = z\n .object({\n // URL of the remote server (e.g., https://agent.sparkecode.com)\n url: z.string().url().optional(),\n // Auth key for the remote server (auto-generated on first use if not set)\n // Can also be set via SPARKECODER_AUTH_KEY env var\n authKey: z.string().optional(),\n })\n .optional();\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-6'),\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 // Public URL for web UI to connect to API (for Docker/remote access)\n // If not set, defaults to http://{host}:{port}\n publicUrl: z.string().url().optional(),\n })\n .default({ port: 3141, host: '127.0.0.1' }),\n\n // Database path (used for local SQLite - ignored if remoteServer is configured)\n databasePath: z.string().optional().default('./sparkecoder.db'),\n\n // Remote server configuration (for centralized storage)\n // If configured, uses remote MongoDB instead of local SQLite\n remoteServer: RemoteServerConfigSchema,\n\n // Vector Gateway configuration for semantic search\n vectorGateway: VectorGatewayConfigSchema,\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 VectorGatewayConfig = z.infer<typeof VectorGatewayConfigSchema>;\nexport type RemoteServerConfig = z.infer<typeof RemoteServerConfigSchema>;\nexport type SparkcoderConfig = z.infer<typeof SparkcoderConfigSchema>;\n\n// Discovered skill sources\nexport interface DiscoveredSkills {\n // Directories where all skills are always loaded\n alwaysLoadedDirs: Array<{ path: string; priority: number }>;\n // Directories where skills are on-demand (frontmatter can override)\n onDemandDirs: Array<{ path: string; priority: number }>;\n // Path to AGENTS.md if it exists (always loaded)\n agentsMdPath: string | null;\n // All directories in priority order (for deduplication)\n allDirectories: string[];\n}\n\n// Resolved vector gateway config with env var overrides applied\nexport interface ResolvedVectorGatewayConfig {\n redisUrl: string | null;\n httpUrl: string | null;\n embeddingModel: string;\n namespace: string | null;\n include: string[];\n exclude: string[];\n}\n\n// Resolved remote server config\nexport interface ResolvedRemoteServerConfig {\n url: string | null;\n authKey: string | null;\n isConfigured: boolean;\n}\n\n// Runtime config with resolved paths\nexport interface ResolvedConfig extends Omit<SparkcoderConfig, 'server'> {\n server: {\n port: number;\n host: string;\n publicUrl?: string;\n };\n resolvedWorkingDirectory: string;\n resolvedSkillsDirectories: string[];\n resolvedDatabasePath: string;\n // Enhanced skill discovery\n discoveredSkills: DiscoveredSkills;\n // Resolved vector gateway config (with env var overrides)\n resolvedVectorGateway: ResolvedVectorGatewayConfig;\n // Resolved remote server config (with env var overrides)\n resolvedRemoteServer: ResolvedRemoteServerConfig;\n}\n","import { readFile, readdir } from 'node:fs/promises';\nimport { resolve, basename, extname, relative } from 'node:path';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { minimatch } from 'minimatch';\nimport { SkillMetadata, SkillMetadataSchema, SkillLoadType, DiscoveredSkills } 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 // Enhanced properties\n alwaysApply: boolean;\n globs: string[];\n loadType: SkillLoadType;\n priority: number; // Lower = higher priority for deduplication\n sourceDir: string; // Which directory this skill came from\n}\n\nexport interface SkillWithContent extends Skill {\n content: string;\n}\n\n/**\n * Parse skill metadata from frontmatter\n * Handles YAML-like format including arrays for globs\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 // Parse YAML-like frontmatter\n const lines = frontmatter.split('\\n');\n const data: Record<string, unknown> = {};\n let currentArray: string[] | null = null;\n let currentArrayKey: string | null = null;\n \n for (const line of lines) {\n // Check if this is an array item (starts with -)\n if (currentArrayKey && line.trim().startsWith('-')) {\n let value = line.trim().slice(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 currentArray?.push(value);\n continue;\n }\n \n // Close any open array when we hit a non-array line\n if (currentArrayKey && currentArray) {\n data[currentArrayKey] = currentArray;\n currentArray = null;\n currentArrayKey = null;\n }\n \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 \n // Check if this starts an array (empty value followed by - items)\n if (value === '' || value === '[]') {\n currentArrayKey = key;\n currentArray = [];\n continue;\n }\n \n // Handle inline arrays like globs: [\"*.tsx\", \"*.jsx\"]\n if (value.startsWith('[') && value.endsWith(']')) {\n const arrayContent = value.slice(1, -1);\n const items = arrayContent.split(',').map(item => {\n let trimmed = item.trim();\n if ((trimmed.startsWith('\"') && trimmed.endsWith('\"')) ||\n (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))) {\n trimmed = trimmed.slice(1, -1);\n }\n return trimmed;\n }).filter(item => item.length > 0);\n data[key] = items;\n continue;\n }\n \n // Remove quotes if present\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n \n // Handle boolean values\n if (value === 'true') {\n data[key] = true;\n } else if (value === 'false') {\n data[key] = false;\n } else {\n data[key] = value;\n }\n }\n }\n \n // Close any remaining open array\n if (currentArrayKey && currentArray) {\n data[currentArrayKey] = currentArray;\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 * Options for loading skills from a directory\n */\ninterface LoadSkillsOptions {\n // Priority for deduplication (lower = higher priority)\n priority?: number;\n // Default load type if not specified in frontmatter\n defaultLoadType?: SkillLoadType;\n // Force alwaysApply for all skills in this directory\n forceAlwaysApply?: boolean;\n}\n\n/**\n * Load all skills from a directory (metadata only)\n */\nexport async function loadSkillsFromDirectory(\n directory: string,\n options: LoadSkillsOptions = {}\n): Promise<Skill[]> {\n const {\n priority = 50,\n defaultLoadType = 'on_demand',\n forceAlwaysApply = false,\n } = options;\n\n if (!existsSync(directory)) {\n return [];\n }\n\n const skills: Skill[] = [];\n const entries = await readdir(directory, { withFileTypes: true });\n\n for (const entry of entries) {\n // Handle both files and directories (for Claude-style SKILL.md in subdirs)\n let filePath: string;\n let fileName: string;\n\n if (entry.isDirectory()) {\n // Check for SKILL.md inside the directory (Claude format)\n const skillMdPath = resolve(directory, entry.name, 'SKILL.md');\n if (existsSync(skillMdPath)) {\n filePath = skillMdPath;\n fileName = entry.name;\n } else {\n continue;\n }\n } else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdc')) {\n filePath = resolve(directory, entry.name);\n fileName = entry.name;\n } else {\n continue;\n }\n\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n\n if (parsed) {\n const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;\n const loadType: SkillLoadType = alwaysApply ? 'always' : defaultLoadType;\n\n skills.push({\n name: parsed.metadata.name,\n description: parsed.metadata.description,\n filePath,\n alwaysApply,\n globs: parsed.metadata.globs,\n loadType,\n priority,\n sourceDir: directory,\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 alwaysApply: forceAlwaysApply,\n globs: [],\n loadType: forceAlwaysApply ? 'always' : defaultLoadType,\n priority,\n sourceDir: directory,\n });\n }\n }\n\n return skills;\n}\n\n/**\n * Load all skills from multiple directories (legacy function for backwards compatibility)\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 all skills from discovered directories with proper priority and typing\n */\nexport async function loadAllSkillsFromDiscovered(\n discovered: DiscoveredSkills\n): Promise<{ always: SkillWithContent[]; onDemand: Skill[]; all: Skill[] }> {\n const allSkills: Skill[] = [];\n const seenNames = new Set<string>();\n\n // Load from always-loaded directories (force alwaysApply = true)\n for (const { path, priority } of discovered.alwaysLoadedDirs) {\n const skills = await loadSkillsFromDirectory(path, {\n priority,\n defaultLoadType: 'always',\n forceAlwaysApply: true,\n });\n for (const skill of skills) {\n if (!seenNames.has(skill.name.toLowerCase())) {\n seenNames.add(skill.name.toLowerCase());\n allSkills.push(skill);\n }\n }\n }\n\n // Load from on-demand directories (respect frontmatter)\n for (const { path, priority } of discovered.onDemandDirs) {\n const skills = await loadSkillsFromDirectory(path, {\n priority,\n defaultLoadType: 'on_demand',\n forceAlwaysApply: false,\n });\n for (const skill of skills) {\n if (!seenNames.has(skill.name.toLowerCase())) {\n seenNames.add(skill.name.toLowerCase());\n allSkills.push(skill);\n }\n }\n }\n\n // Separate into always-loaded (with content) and on-demand\n const alwaysSkills = allSkills.filter(s => s.alwaysApply || s.loadType === 'always');\n const onDemandSkills = allSkills.filter(s => !s.alwaysApply && s.loadType !== 'always');\n\n // Load content for always-applied skills\n const alwaysWithContent: SkillWithContent[] = await Promise.all(\n alwaysSkills.map(async (skill) => {\n const content = await readFile(skill.filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n return {\n ...skill,\n content: parsed ? parsed.body : content,\n };\n })\n );\n\n return {\n always: alwaysWithContent,\n onDemand: onDemandSkills,\n all: allSkills,\n };\n}\n\n/**\n * Get skills that should be auto-injected based on glob patterns matching active files\n */\nexport async function getGlobMatchedSkills(\n skills: Skill[],\n activeFiles: string[],\n workingDirectory: string\n): Promise<SkillWithContent[]> {\n if (activeFiles.length === 0) {\n return [];\n }\n\n // Normalize active files to relative paths\n const relativeFiles = activeFiles.map(f => {\n if (f.startsWith(workingDirectory)) {\n return relative(workingDirectory, f);\n }\n return f;\n });\n\n // Find skills with matching globs that aren't already always-applied\n const matchedSkills = skills.filter(skill => {\n // Skip if already always applied (those are loaded separately)\n if (skill.alwaysApply || skill.loadType === 'always') {\n return false;\n }\n\n // Skip if no globs defined\n if (!skill.globs || skill.globs.length === 0) {\n return false;\n }\n\n // Check if any active file matches any glob\n return relativeFiles.some(file =>\n skill.globs.some(pattern => minimatch(file, pattern, { matchBase: true }))\n );\n });\n\n // Load content for matched skills\n const matchedWithContent: SkillWithContent[] = await Promise.all(\n matchedSkills.map(async (skill) => {\n const content = await readFile(skill.filePath, 'utf-8');\n const parsed = parseSkillFrontmatter(content);\n return {\n ...skill,\n content: parsed ? parsed.body : content,\n loadType: 'glob_matched' as SkillLoadType,\n };\n })\n );\n\n return matchedWithContent;\n}\n\n/**\n * Load AGENTS.md content if it exists\n */\nexport async function loadAgentsMd(agentsMdPath: string | null): Promise<string | null> {\n if (!agentsMdPath || !existsSync(agentsMdPath)) {\n return null;\n }\n\n const content = await readFile(agentsMdPath, 'utf-8');\n return content;\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 on-demand skills list for context (shows as available to load)\n */\nexport function formatSkillsForContext(skills: Skill[]): string {\n // Filter to only on-demand skills\n const onDemandSkills = skills.filter(s => !s.alwaysApply && s.loadType !== 'always');\n\n if (onDemandSkills.length === 0) {\n return 'No on-demand skills available.';\n }\n\n const lines = ['Available skills (use load_skill tool to load into context):'];\n for (const skill of onDemandSkills) {\n const globInfo = skill.globs?.length ? ` [auto-loads for: ${skill.globs.join(', ')}]` : '';\n lines.push(`- ${skill.name}: ${skill.description}${globInfo}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format always-loaded skills content for injection into system prompt\n */\nexport function formatAlwaysLoadedSkills(skills: SkillWithContent[]): string {\n if (skills.length === 0) {\n return '';\n }\n\n const sections: string[] = [];\n \n for (const skill of skills) {\n sections.push(`### ${skill.name}\\n\\n${skill.content}`);\n }\n\n return `## Active Rules & Skills (Always Loaded)\\n\\n${sections.join('\\n\\n---\\n\\n')}`;\n}\n\n/**\n * Format glob-matched skills content for injection into system prompt\n */\nexport function formatGlobMatchedSkills(skills: SkillWithContent[]): string {\n if (skills.length === 0) {\n return '';\n }\n\n const sections: string[] = [];\n \n for (const skill of skills) {\n sections.push(`### ${skill.name}\\n\\n${skill.content}`);\n }\n\n return `## Context-Relevant Skills (Auto-loaded based on active files)\\n\\n${sections.join('\\n\\n---\\n\\n')}`;\n}\n\n/**\n * Format AGENTS.md content for injection\n */\nexport function formatAgentsMdContent(content: string | null): string {\n if (!content) {\n return '';\n }\n\n return `## Project Instructions (AGENTS.md)\\n\\n${content}`;\n}\n","import {\n streamText,\n generateText,\n tool,\n stepCountIs,\n type ToolSet,\n type ModelMessage,\n} from 'ai';\nimport { isAnthropicModel, resolveModel } from './model.js';\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, WriteFileProgress, SearchToolProgress } 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\n/** Attachment for user messages (images, files) */\nexport interface MessageAttachment {\n type: 'image' | 'file';\n data: string; // base64 data URL or raw base64\n mediaType?: string;\n filename?: string; // Original filename for context\n savedPath?: string; // Path where file was saved on disk\n}\n\nexport interface AgentRunOptions {\n prompt: string;\n /** Optional file/image attachments to include in the message */\n attachments?: MessageAttachment[];\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, write_file, or explore_agent) has progress to report */\n onToolProgress?: (progress: { toolName: string; data: BashToolProgress | WriteFileProgress | SearchToolProgress }) => 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 async createToolsWithCallbacks(options: {\n onToolProgress?: AgentRunOptions['onToolProgress'];\n }): Promise<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 onWriteFileProgress: options.onToolProgress\n ? (progress) => options.onToolProgress!({ toolName: 'write_file', data: progress })\n : undefined,\n onSearchProgress: options.onToolProgress\n ? (progress) => options.onToolProgress!({ toolName: 'explore_agent', 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 = await sessionQueries.getById(options.sessionId);\n if (!existing) {\n throw new Error(`Session not found: ${options.sessionId}`);\n }\n session = existing;\n } else {\n session = await 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 = await 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 * Build user message content from prompt and attachments\n */\n private buildUserMessageContent(\n prompt: string,\n attachments?: MessageAttachment[]\n ): string | Array<{ type: string; text?: string; image?: string; data?: string; mediaType?: string; filename?: string; savedPath?: string }> {\n if (!attachments || attachments.length === 0) {\n return prompt;\n }\n\n // Build content array with text and file parts\n const contentParts: Array<{ type: string; text?: string; image?: string; data?: string; mediaType?: string; filename?: string; savedPath?: string }> = [];\n \n // IMPORTANT: Put file location info FIRST so the model knows where files are saved\n // This gives the model context about file paths before it sees the images\n const attachmentDescriptions = attachments\n .map((a, i) => {\n const name = a.filename || `attachment_${i + 1}`;\n const typeLabel = a.type === 'image' ? 'Image' : 'File';\n const location = a.savedPath || '(path unknown)';\n return `${i + 1}. ${typeLabel}: \"${name}\" saved at: ${location}`;\n })\n .join('\\n');\n \n contentParts.push({ \n type: 'text', \n text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]\\n${attachmentDescriptions}\\n\\nYou can reference these files by their paths above. The file contents are also shown inline below.` \n });\n \n // Add user's text prompt\n if (prompt) {\n contentParts.push({ type: 'text', text: `\\n[USER MESSAGE]\\n${prompt}` });\n }\n \n // Add file/image parts with filename and path metadata\n for (const attachment of attachments) {\n if (attachment.type === 'image') {\n contentParts.push({\n type: 'image',\n image: attachment.data, // base64 data URL or raw base64\n mediaType: attachment.mediaType,\n filename: attachment.filename,\n savedPath: attachment.savedPath,\n });\n } else {\n contentParts.push({\n type: 'file',\n data: attachment.data,\n mediaType: attachment.mediaType || 'application/octet-stream',\n filename: attachment.filename,\n savedPath: attachment.savedPath,\n });\n }\n }\n \n return contentParts;\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 // Build user message content with attachments\n const userContent = this.buildUserMessageContent(options.prompt, options.attachments);\n\n // Add user message to context (skip if already saved externally)\n if (!options.skipSaveUserMessage) {\n this.context.addUserMessage(userContent);\n }\n\n // Update session status\n await sessionQueries.updateStatus(this.session.id, 'active');\n\n // Build system prompt with enhanced skill discovery\n const systemPrompt = await buildSystemPrompt({\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n sessionId: this.session.id,\n discoveredSkills: config.discoveredSkills,\n // TODO: Pass activeFiles from client for glob matching\n activeFiles: [],\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 ? await 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 useAnthropic = isAnthropicModel(this.session.model);\n const stream = streamText({\n model: resolveModel(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: useAnthropic\n ? {\n anthropic: {\n toolStreaming: true,\n thinking: {\n type: 'enabled',\n budgetTokens: 10000,\n },\n },\n }\n : undefined,\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 with enhanced skill discovery\n const systemPrompt = await buildSystemPrompt({\n workingDirectory: this.session.workingDirectory,\n skillsDirectories: config.resolvedSkillsDirectories,\n sessionId: this.session.id,\n discoveredSkills: config.discoveredSkills,\n activeFiles: [],\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 ? await 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 useAnthropic = isAnthropicModel(this.session.model);\n const result = await generateText({\n model: resolveModel(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: useAnthropic\n ? {\n anthropic: {\n thinking: {\n type: 'enabled',\n budgetTokens: 10000,\n },\n },\n }\n : undefined,\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, await execution);\n\n // Notify about approval requirement\n options.onApprovalRequired?.(await execution);\n\n // Update session status\n await 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 const exec = await execution;\n if (!approved) {\n // Tool was rejected\n const reason = resolverData?.reason || 'User rejected the tool execution';\n await toolExecutionQueries.reject(exec.id);\n await 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 await toolExecutionQueries.approve(exec.id);\n await sessionQueries.updateStatus(this.session.id, 'active');\n\n try {\n const result = await (originalTool as any).execute(input, toolOptions);\n await toolExecutionQueries.complete(exec.id, result);\n return result;\n } catch (error: any) {\n await toolExecutionQueries.complete(exec.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 = await toolExecutionQueries.getPendingApprovals(this.session.id);\n const execution = pendingFromDb.find((e: ToolExecution) => 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 await toolExecutionQueries.approve(execution.id);\n return { approved: true };\n }\n\n /**\n * Reject a pending tool execution\n */\n async reject(toolCallId: string, reason?: string): Promise<{ 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 = await toolExecutionQueries.getPendingApprovals(this.session.id);\n const execution = pendingFromDb.find((e: ToolExecution) => 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 await toolExecutionQueries.reject(execution.id);\n return { rejected: true };\n }\n\n /**\n * Get pending approvals\n */\n async getPendingApprovals(): Promise<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 { gateway } from '@ai-sdk/gateway';\nimport type { LanguageModel } from 'ai';\n\nconst ANTHROPIC_PREFIX = 'anthropic/';\nconst GOOGLE_PREFIX = 'google/';\n\n/**\n * Check if a model ID is an Anthropic model (for provider-specific options).\n */\nexport function isAnthropicModel(modelId: string): boolean {\n const normalized = modelId.trim().toLowerCase();\n return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith('claude-');\n}\n\n/**\n * Check if a model ID is a Google model.\n */\nexport function isGoogleModel(modelId: string): boolean {\n const normalized = modelId.trim().toLowerCase();\n return normalized.startsWith(GOOGLE_PREFIX) || normalized.startsWith('gemini-');\n}\n\n/**\n * Resolves a model ID to a LanguageModel instance.\n * \n * All models are routed through the Vercel AI Gateway.\n */\nexport function resolveModel(modelId: string): LanguageModel {\n return gateway(modelId.trim());\n}\n\n// Default models for subagents (smaller, faster models)\nexport const SUBAGENT_MODELS = {\n search: 'google/gemini-3-flash-preview',\n analyze: 'google/gemini-3-flash-preview',\n default: 'google/gemini-3-flash-preview',\n} as const;\n","/**\n * Remote database client\n * \n * Implements the same interface as the local SQLite database\n * but calls the remote server via HTTP.\n */\n\nimport type {\n Session,\n Message,\n ToolExecution,\n TodoItem,\n ModelMessage,\n Terminal,\n ActiveStream,\n Checkpoint,\n FileBackup,\n SubagentExecution,\n SubagentStep,\n IndexedChunk,\n IndexStatusRecord,\n LoadedSkill,\n} from './schema.js';\n\nlet remoteServerUrl: string | null = null;\nlet authKey: string | null = null;\n\n/**\n * Initialize the remote database client\n */\nexport function initRemoteDatabase(serverUrl: string, key: string) {\n remoteServerUrl = serverUrl.replace(/\\/$/, ''); // Remove trailing slash\n authKey = key;\n}\n\n/**\n * Close the remote client (no-op, just for API compatibility)\n */\nexport function closeRemoteDatabase() {\n remoteServerUrl = null;\n authKey = null;\n}\n\n/**\n * Check if remote database is configured\n */\nexport function isRemoteConfigured(): boolean {\n return !!remoteServerUrl && !!authKey;\n}\n\n/**\n * Date fields that should be parsed from ISO strings to Date objects.\n * These are top-level metadata fields on database records (Session, Message, etc.),\n * NOT fields inside modelMessage content (tool outputs, etc.).\n */\nconst DATE_FIELDS = ['createdAt', 'updatedAt', 'startedAt', 'completedAt', 'stoppedAt', 'finishedAt', 'loadedAt', 'indexedAt', 'lastFullIndex', 'lastIncrementalIndex'];\n\n/**\n * Fields that contain AI SDK ModelMessage data and should NOT be recursively\n * processed by parseDates. The AI SDK's Zod schema requires tool output values\n * to be valid JSON primitives (string, number, boolean, null, object, array).\n * Converting date strings to Date objects inside these fields corrupts them and\n * causes AI_InvalidPromptError when the messages are passed back to streamText().\n */\nconst MODEL_MESSAGE_FIELDS = ['modelMessage', 'modelMessages'];\n\n/**\n * Parse date strings to Date objects on top-level record fields only.\n * \n * IMPORTANT: Does NOT recurse into `modelMessage` / `modelMessages` fields.\n * Those contain AI SDK ModelMessage data that must remain JSON-serializable.\n * Recursing into them converts date strings (e.g. `createdAt` inside tool\n * result outputs) to Date objects, which violates the AI SDK's jsonValueSchema\n * and triggers AI_InvalidPromptError on subsequent streamText() calls.\n */\nfunction parseDates(obj: any): any {\n if (obj === null || obj === undefined) return obj;\n if (Array.isArray(obj)) return obj.map(parseDates);\n if (typeof obj !== 'object' || obj instanceof Date) return obj;\n \n const result = { ...obj };\n for (const key of Object.keys(result)) {\n // Skip modelMessage fields entirely - these must stay JSON-serializable\n if (MODEL_MESSAGE_FIELDS.includes(key)) {\n continue;\n }\n if (DATE_FIELDS.includes(key) && typeof result[key] === 'string') {\n result[key] = new Date(result[key]);\n } else if (typeof result[key] === 'object') {\n result[key] = parseDates(result[key]);\n }\n }\n return result;\n}\n\n/**\n * HTTP helper for remote API calls\n * @param options.skipParseDates - If true, skip the parseDates post-processing.\n * Use for endpoints that return ModelMessage[] directly, since those must\n * remain JSON-serializable for the AI SDK.\n */\nasync function api<T>(\n path: string,\n options: { method?: string; body?: unknown; skipParseDates?: boolean } = {}\n): Promise<T> {\n if (!remoteServerUrl || !authKey) {\n throw new Error('Remote database not initialized');\n }\n \n const url = `${remoteServerUrl}/db${path}`;\n const init: RequestInit = {\n method: options.method || 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${authKey}`,\n },\n };\n \n if (options.body) {\n init.body = JSON.stringify(options.body);\n }\n \n const response = await fetch(url, init);\n \n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n throw new Error(error.error || `HTTP ${response.status}`);\n }\n \n const text = await response.text();\n if (!text || text === 'null') {\n return null as T;\n }\n \n const parsed = JSON.parse(text);\n\n // Skip date parsing for raw ModelMessage data - it must stay JSON-serializable\n if (options.skipParseDates) {\n return parsed as T;\n }\n\n // Parse JSON and convert date strings to Date objects\n return parseDates(parsed) as T;\n}\n\n// ============================================\n// Session Queries\n// ============================================\n\nexport const remoteSessionQueries = {\n create(data: { workingDirectory: string; model: string; name?: string; config?: any }): Promise<Session> {\n return api<Session>('/sessions', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<Session | undefined> {\n return api<Session | undefined>(`/sessions/${id}`).catch(() => undefined);\n },\n\n list(limit = 50, offset = 0): Promise<Session[]> {\n return api<Session[]>(`/sessions?limit=${limit}&offset=${offset}`);\n },\n\n updateStatus(id: string, status: Session['status']): Promise<Session | undefined> {\n return api<Session | undefined>(`/sessions/${id}`, { method: 'PATCH', body: { status } });\n },\n\n updateModel(id: string, model: string): Promise<Session | undefined> {\n return api<Session | undefined>(`/sessions/${id}`, { method: 'PATCH', body: { model } });\n },\n\n update(id: string, updates: { model?: string; name?: string; config?: any }): Promise<Session | undefined> {\n return api<Session | undefined>(`/sessions/${id}`, { method: 'PATCH', body: updates });\n },\n\n delete(id: string): Promise<boolean> {\n return api<{ success: boolean }>(`/sessions/${id}`, { method: 'DELETE' }).then(r => r?.success ?? false);\n },\n};\n\n// ============================================\n// Message Queries\n// ============================================\n\nexport const remoteMessageQueries = {\n async getNextSequence(sessionId: string): Promise<number> {\n const result = await api<{ nextSequence: number }>(`/messages/session/${sessionId}/next-sequence`);\n return result.nextSequence;\n },\n\n create(sessionId: string, modelMessage: ModelMessage): Promise<Message> {\n return api<Message>('/messages', { method: 'POST', body: { sessionId, modelMessage } });\n },\n\n addMany(sessionId: string, modelMessages: ModelMessage[]): Promise<Message[]> {\n return api<Message[]>('/messages/batch', { method: 'POST', body: { sessionId, modelMessages } });\n },\n\n getBySession(sessionId: string): Promise<Message[]> {\n return api<Message[]>(`/messages/session/${sessionId}`);\n },\n\n getModelMessages(sessionId: string): Promise<ModelMessage[]> {\n // IMPORTANT: skipParseDates=true because ModelMessage data must remain\n // JSON-serializable. The parseDates function would convert date strings\n // inside tool result outputs (e.g. todo items with createdAt) to Date\n // objects, which violates the AI SDK's jsonValueSchema and causes\n // AI_InvalidPromptError on subsequent streamText() calls.\n return api<ModelMessage[]>(`/messages/session/${sessionId}/model-messages`, { skipParseDates: true });\n },\n\n async getRecentBySession(sessionId: string, limit = 50): Promise<Message[]> {\n const messages = await api<Message[]>(`/messages/session/${sessionId}`);\n return messages.slice(-limit);\n },\n\n async countBySession(sessionId: string): Promise<number> {\n const result = await api<{ count: number }>(`/messages/session/${sessionId}/count`);\n return result.count;\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/messages/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n\n async deleteFromSequence(sessionId: string, fromSequence: number): Promise<number> {\n const result = await api<{ deleted: number }>(\n `/messages/session/${sessionId}/from-sequence/${fromSequence}`,\n { method: 'DELETE' }\n );\n return result.deleted;\n },\n};\n\n// ============================================\n// Tool Execution Queries\n// ============================================\n\nexport const remoteToolExecutionQueries = {\n create(data: {\n sessionId: string;\n messageId?: string;\n toolName: string;\n toolCallId: string;\n input?: any;\n requiresApproval?: boolean;\n status?: 'pending' | 'approved' | 'rejected' | 'completed' | 'error';\n }): Promise<ToolExecution> {\n return api<ToolExecution>('/tool-executions', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/${id}`).catch(() => undefined);\n },\n\n getByToolCallId(toolCallId: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/by-tool-call-id/${toolCallId}`).catch(() => undefined);\n },\n\n getPendingApprovals(sessionId: string): Promise<ToolExecution[]> {\n return api<ToolExecution[]>(`/tool-executions/session/${sessionId}/pending`);\n },\n\n approve(id: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/${id}`, { method: 'PATCH', body: { status: 'approved' } });\n },\n\n reject(id: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/${id}`, { method: 'PATCH', body: { status: 'rejected' } });\n },\n\n complete(id: string, output: unknown, error?: string): Promise<ToolExecution | undefined> {\n return api<ToolExecution | undefined>(`/tool-executions/${id}`, {\n method: 'PATCH',\n body: { status: error ? 'error' : 'completed', output, error },\n });\n },\n\n getBySession(sessionId: string): Promise<ToolExecution[]> {\n return api<ToolExecution[]>(`/tool-executions/session/${sessionId}`);\n },\n\n async deleteAfterTime(sessionId: string, afterTime: Date | string): Promise<number> {\n // Handle both Date objects and ISO strings\n const timestamp = afterTime instanceof Date ? afterTime.getTime() : new Date(afterTime).getTime();\n const result = await api<{ deleted: number }>(\n `/tool-executions/session/${sessionId}/after/${timestamp}`,\n { method: 'DELETE' }\n );\n return result.deleted;\n },\n};\n\n// ============================================\n// Todo Queries\n// ============================================\n\nexport const remoteTodoQueries = {\n create(data: { sessionId: string; content: string; order?: number }): Promise<TodoItem> {\n return api<TodoItem>('/todos', { method: 'POST', body: data });\n },\n\n createMany(sessionId: string, items: Array<{ content: string; order?: number }>): Promise<TodoItem[]> {\n return api<TodoItem[]>('/todos/batch', { method: 'POST', body: { sessionId, items } });\n },\n\n getBySession(sessionId: string): Promise<TodoItem[]> {\n return api<TodoItem[]>(`/todos/session/${sessionId}`);\n },\n\n updateStatus(id: string, status: TodoItem['status']): Promise<TodoItem | undefined> {\n return api<TodoItem | undefined>(`/todos/${id}`, { method: 'PATCH', body: { status } });\n },\n\n async delete(id: string): Promise<boolean> {\n const result = await api<{ success: boolean }>(`/todos/${id}`, { method: 'DELETE' });\n return result?.success ?? false;\n },\n\n async clearSession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/todos/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Skill Queries\n// ============================================\n\nexport const remoteSkillQueries = {\n load(sessionId: string, skillName: string): Promise<LoadedSkill> {\n return api<LoadedSkill>('/skills', { method: 'POST', body: { sessionId, skillName } });\n },\n\n getBySession(sessionId: string): Promise<LoadedSkill[]> {\n return api<LoadedSkill[]>(`/skills/session/${sessionId}`);\n },\n\n async isLoaded(sessionId: string, skillName: string): Promise<boolean> {\n const result = await api<{ isLoaded: boolean }>(`/skills/session/${sessionId}/is-loaded/${skillName}`);\n return result.isLoaded;\n },\n};\n\n// ============================================\n// Terminal Queries\n// ============================================\n\nexport const remoteTerminalQueries = {\n create(data: { sessionId: string; command: string; cwd: string; name?: string }): Promise<Terminal> {\n return api<Terminal>('/terminals', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<Terminal | undefined> {\n return api<Terminal | undefined>(`/terminals/${id}`).catch(() => undefined);\n },\n\n getBySession(sessionId: string): Promise<Terminal[]> {\n return api<Terminal[]>(`/terminals/session/${sessionId}`);\n },\n\n getRunning(sessionId: string): Promise<Terminal[]> {\n return api<Terminal[]>(`/terminals/session/${sessionId}/running`);\n },\n\n updateStatus(id: string, status: Terminal['status'], exitCode?: number, error?: string): Promise<Terminal | undefined> {\n return api<Terminal | undefined>(`/terminals/${id}`, { method: 'PATCH', body: { status, exitCode, error } });\n },\n\n updatePid(id: string, pid: number): Promise<Terminal | undefined> {\n return api<Terminal | undefined>(`/terminals/${id}`, { method: 'PATCH', body: { pid } });\n },\n\n async delete(id: string): Promise<boolean> {\n const result = await api<{ success: boolean }>(`/terminals/${id}`, { method: 'DELETE' });\n return result?.success ?? false;\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/terminals/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Active Stream Queries\n// ============================================\n\nexport const remoteActiveStreamQueries = {\n create(sessionId: string, streamId: string): Promise<ActiveStream> {\n return api<ActiveStream>('/streams', { method: 'POST', body: { sessionId, streamId } });\n },\n\n getBySessionId(sessionId: string): Promise<ActiveStream | undefined> {\n return api<ActiveStream | null>(`/streams/session/${sessionId}`).then(r => r ?? undefined);\n },\n\n getByStreamId(streamId: string): Promise<ActiveStream | undefined> {\n return api<ActiveStream | undefined>(`/streams/by-stream-id/${streamId}`).catch(() => undefined);\n },\n\n finish(streamId: string): Promise<ActiveStream | undefined> {\n return api<ActiveStream | undefined>(`/streams/by-stream-id/${streamId}`, { method: 'PATCH', body: { status: 'finished' } });\n },\n\n markError(streamId: string): Promise<ActiveStream | undefined> {\n return api<ActiveStream | undefined>(`/streams/by-stream-id/${streamId}`, { method: 'PATCH', body: { status: 'error' } });\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/streams/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Checkpoint Queries\n// ============================================\n\nexport const remoteCheckpointQueries = {\n create(data: { sessionId: string; messageSequence: number; gitHead?: string }): Promise<Checkpoint> {\n return api<Checkpoint>('/checkpoints', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<Checkpoint | undefined> {\n return api<Checkpoint | undefined>(`/checkpoints/${id}`).catch(() => undefined);\n },\n\n getBySession(sessionId: string): Promise<Checkpoint[]> {\n return api<Checkpoint[]>(`/checkpoints/session/${sessionId}`);\n },\n\n getByMessageSequence(sessionId: string, messageSequence: number): Promise<Checkpoint | undefined> {\n return api<Checkpoint | null>(`/checkpoints/session/${sessionId}/by-sequence/${messageSequence}`).then(r => r ?? undefined);\n },\n\n getLatest(sessionId: string): Promise<Checkpoint | undefined> {\n return api<Checkpoint | null>(`/checkpoints/session/${sessionId}/latest`).then(r => r ?? undefined);\n },\n\n async deleteAfterSequence(sessionId: string, messageSequence: number): Promise<number> {\n const result = await api<{ deleted: number }>(\n `/checkpoints/session/${sessionId}/after-sequence/${messageSequence}`,\n { method: 'DELETE' }\n );\n return result.deleted;\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/checkpoints/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// File Backup Queries\n// ============================================\n\nexport const remoteFileBackupQueries = {\n create(data: {\n checkpointId: string;\n sessionId: string;\n filePath: string;\n originalContent: string | null;\n existed: boolean;\n }): Promise<FileBackup> {\n return api<FileBackup>('/file-backups', { method: 'POST', body: data });\n },\n\n getByCheckpoint(checkpointId: string): Promise<FileBackup[]> {\n return api<FileBackup[]>(`/file-backups/checkpoint/${checkpointId}`);\n },\n\n getBySession(sessionId: string): Promise<FileBackup[]> {\n return api<FileBackup[]>(`/file-backups/session/${sessionId}`);\n },\n\n getFromSequence(sessionId: string, messageSequence: number): Promise<FileBackup[]> {\n return api<FileBackup[]>(`/file-backups/session/${sessionId}/from-sequence/${messageSequence}`);\n },\n\n async hasBackup(checkpointId: string, filePath: string): Promise<boolean> {\n const result = await api<{ hasBackup: boolean }>(\n `/file-backups/checkpoint/${checkpointId}/has-backup/${encodeURIComponent(filePath)}`\n );\n return result.hasBackup;\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/file-backups/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Subagent Queries\n// ============================================\n\nexport const remoteSubagentQueries = {\n create(data: {\n sessionId: string;\n toolCallId: string;\n subagentType: string;\n task: string;\n model: string;\n }): Promise<SubagentExecution> {\n return api<SubagentExecution>('/subagents', { method: 'POST', body: data });\n },\n\n getById(id: string): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}`).catch(() => undefined);\n },\n\n getByToolCallId(toolCallId: string): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/by-tool-call-id/${toolCallId}`).catch(() => undefined);\n },\n\n getBySession(sessionId: string): Promise<SubagentExecution[]> {\n return api<SubagentExecution[]>(`/subagents/session/${sessionId}`);\n },\n\n addStep(id: string, step: SubagentStep): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}/add-step`, { method: 'POST', body: { step } }).catch(() => undefined);\n },\n\n complete(id: string, result: unknown): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}`, { method: 'PATCH', body: { status: 'completed', result } }).catch(() => undefined);\n },\n\n markError(id: string, error: string): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}`, { method: 'PATCH', body: { status: 'error', error } }).catch(() => undefined);\n },\n\n cancel(id: string): Promise<SubagentExecution | undefined> {\n return api<SubagentExecution | undefined>(`/subagents/${id}`, { method: 'PATCH', body: { status: 'cancelled' } }).catch(() => undefined);\n },\n\n async deleteBySession(sessionId: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/subagents/session/${sessionId}`, { method: 'DELETE' });\n return result.deleted;\n },\n};\n\n// ============================================\n// Indexed Chunk Queries\n// ============================================\n\nexport const remoteIndexedChunkQueries = {\n upsert(\n _db: any, // Ignored - for API compatibility\n data: {\n id: string;\n contentHash: string;\n filePath: string;\n repoNamespace: string;\n startLine?: number;\n endLine?: number;\n language?: string;\n }\n ): Promise<IndexedChunk> {\n return api<IndexedChunk>('/indexed-chunks', { method: 'POST', body: data });\n },\n\n batchUpsert(\n _db: any,\n chunks: Array<{\n id: string;\n contentHash: string;\n filePath: string;\n repoNamespace: string;\n startLine?: number;\n endLine?: number;\n language?: string;\n }>\n ): Promise<{ created: number; updated: number }> {\n return api<{ created: number; updated: number }>('/indexed-chunks/batch', { \n method: 'POST', \n body: { chunks } \n });\n },\n\n getById(_db: any, id: string): Promise<IndexedChunk | undefined> {\n return api<IndexedChunk | undefined>(`/indexed-chunks/${id}`).catch(() => undefined);\n },\n\n getByNamespace(_db: any, namespace: string): Promise<IndexedChunk[]> {\n return api<IndexedChunk[]>(`/indexed-chunks/namespace/${namespace}`);\n },\n\n getByFilePath(_db: any, namespace: string, filePath: string): Promise<IndexedChunk[]> {\n return api<IndexedChunk[]>(`/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`);\n },\n\n async deleteByNamespace(_db: any, namespace: string): Promise<number> {\n const result = await api<{ deleted: number }>(`/indexed-chunks/namespace/${namespace}`, { method: 'DELETE' });\n return result.deleted;\n },\n\n async deleteByFilePath(_db: any, namespace: string, filePath: string): Promise<number> {\n const result = await api<{ deleted: number }>(\n `/indexed-chunks/namespace/${namespace}/file/${encodeURIComponent(filePath)}`,\n { method: 'DELETE' }\n );\n return result.deleted;\n },\n\n async countByNamespace(_db: any, namespace: string): Promise<number> {\n const result = await api<{ count: number }>(`/indexed-chunks/namespace/${namespace}/count`);\n return result.count;\n },\n};\n\n// ============================================\n// Index Status Queries\n// ============================================\n\nexport const remoteIndexStatusQueries = {\n upsert(\n _db: any, // Ignored\n data: {\n id: string;\n repoNamespace: string;\n totalChunks?: number;\n lastFullIndex?: Date;\n lastIncrementalIndex?: Date;\n }\n ): Promise<IndexStatusRecord> {\n return api<IndexStatusRecord>('/index-status', {\n method: 'POST',\n body: {\n ...data,\n lastFullIndex: data.lastFullIndex?.toISOString(),\n lastIncrementalIndex: data.lastIncrementalIndex?.toISOString(),\n },\n });\n },\n\n get(_db: any, namespace: string): Promise<IndexStatusRecord | undefined> {\n return api<IndexStatusRecord | null>(`/index-status/namespace/${namespace}`).then(r => r ?? undefined);\n },\n\n async delete(_db: any, namespace: string): Promise<boolean> {\n const result = await api<{ success: boolean }>(`/index-status/namespace/${namespace}`, { method: 'DELETE' });\n return result?.success ?? false;\n },\n\n list(_db: any): Promise<IndexStatusRecord[]> {\n return api<IndexStatusRecord[]>('/index-status');\n },\n};\n","/**\n * Database layer - Remote MongoDB only\n * \n * All data is stored on the remote server at agent.sparkecode.com\n */\n\nimport {\n initRemoteDatabase,\n closeRemoteDatabase,\n remoteSessionQueries,\n remoteMessageQueries,\n remoteToolExecutionQueries,\n remoteTodoQueries,\n remoteSkillQueries,\n remoteTerminalQueries,\n remoteActiveStreamQueries,\n remoteCheckpointQueries,\n remoteFileBackupQueries,\n remoteSubagentQueries,\n remoteIndexedChunkQueries,\n remoteIndexStatusQueries,\n} from './remote.js';\n\n// Re-export types from schema\nexport type {\n Session,\n NewSession,\n Message,\n NewMessage,\n ToolExecution,\n NewToolExecution,\n TodoItem,\n NewTodoItem,\n SessionConfig,\n ModelMessage,\n UserModelMessage,\n UserContentPart,\n UserTextPart,\n UserImagePart,\n UserFilePart,\n Terminal,\n NewTerminal,\n ActiveStream,\n NewActiveStream,\n Checkpoint,\n NewCheckpoint,\n FileBackup,\n NewFileBackup,\n SubagentExecution,\n NewSubagentExecution,\n SubagentStep,\n IndexedChunk,\n NewIndexedChunk,\n IndexStatusRecord,\n NewIndexStatusRecord,\n LoadedSkill,\n} from './schema.js';\n\nlet initialized = false;\n\n/**\n * Initialize the database with remote server config\n * @param config - Remote server configuration { url, authKey }\n */\nexport function initDatabase(config: { url: string; authKey: string }) {\n initRemoteDatabase(config.url, config.authKey);\n initialized = true;\n}\n\n/**\n * Get a stub database object for API compatibility\n * Functions that take a db parameter will ignore it for remote operations\n */\nexport function getDb() {\n if (!initialized) {\n throw new Error('Database not initialized. Call initDatabase first.');\n }\n // Return a stub - the actual queries use remote API calls\n return {} as any;\n}\n\n/**\n * Check if using remote database (always true now)\n */\nexport function isUsingRemote(): boolean {\n return true;\n}\n\n/**\n * Close the database connection\n */\nexport function closeDatabase() {\n closeRemoteDatabase();\n initialized = false;\n}\n\n// Re-export query objects with cleaner names\nexport const sessionQueries = remoteSessionQueries;\nexport const messageQueries = remoteMessageQueries;\nexport const toolExecutionQueries = remoteToolExecutionQueries;\nexport const todoQueries = remoteTodoQueries;\nexport const skillQueries = remoteSkillQueries;\nexport const terminalQueries = remoteTerminalQueries;\nexport const activeStreamQueries = remoteActiveStreamQueries;\nexport const checkpointQueries = remoteCheckpointQueries;\nexport const fileBackupQueries = remoteFileBackupQueries;\nexport const subagentQueries = remoteSubagentQueries;\nexport const indexedChunkQueries = remoteIndexedChunkQueries;\nexport const indexStatusQueries = remoteIndexStatusQueries;\n","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 DiscoveredSkills,\n ResolvedVectorGatewayConfig,\n ResolvedRemoteServerConfig,\n} from './types.js';\n\nconst CONFIG_FILE_NAMES = [\n 'sparkecoder.config.json',\n 'sparkecoder.json',\n '.sparkecoder.json',\n];\n\n/**\n * Discover all skill directories in the working directory\n * Searches for:\n * - .sparkecoder/rules/ (always loaded, priority 1)\n * - .sparkecoder/skills/ (on-demand, priority 2)\n * - .cursor/rules/ (parse frontmatter, priority 3)\n * - .claude/skills/ (on-demand, priority 4)\n * - skills/ (legacy, on-demand, priority 5)\n * - AGENTS.md (always loaded)\n */\nexport function discoverSkillDirectories(workingDir: string): DiscoveredSkills {\n const alwaysLoadedDirs: Array<{ path: string; priority: number }> = [];\n const onDemandDirs: Array<{ path: string; priority: number }> = [];\n const allDirectories: string[] = [];\n let agentsMdPath: string | null = null;\n\n // Priority 1: .sparkecoder/rules/ (always loaded)\n const sparkRulesDir = join(workingDir, '.sparkecoder', 'rules');\n if (existsSync(sparkRulesDir)) {\n alwaysLoadedDirs.push({ path: sparkRulesDir, priority: 1 });\n allDirectories.push(sparkRulesDir);\n }\n\n // Priority 2: .sparkecoder/skills/ (on-demand)\n const sparkSkillsDir = join(workingDir, '.sparkecoder', 'skills');\n if (existsSync(sparkSkillsDir)) {\n onDemandDirs.push({ path: sparkSkillsDir, priority: 2 });\n allDirectories.push(sparkSkillsDir);\n }\n\n // Priority 3: .cursor/rules/ (parse frontmatter for alwaysApply)\n const cursorRulesDir = join(workingDir, '.cursor', 'rules');\n if (existsSync(cursorRulesDir)) {\n // Cursor rules can be either - will be determined by frontmatter\n onDemandDirs.push({ path: cursorRulesDir, priority: 3 });\n allDirectories.push(cursorRulesDir);\n }\n\n // Priority 4: .claude/skills/ (on-demand)\n const claudeSkillsDir = join(workingDir, '.claude', 'skills');\n if (existsSync(claudeSkillsDir)) {\n onDemandDirs.push({ path: claudeSkillsDir, priority: 4 });\n allDirectories.push(claudeSkillsDir);\n }\n\n // Priority 5: skills/ (legacy, on-demand)\n const legacySkillsDir = join(workingDir, 'skills');\n if (existsSync(legacySkillsDir)) {\n onDemandDirs.push({ path: legacySkillsDir, priority: 5 });\n allDirectories.push(legacySkillsDir);\n }\n\n // Check for AGENTS.md (always loaded)\n const agentsMd = join(workingDir, 'AGENTS.md');\n if (existsSync(agentsMd)) {\n agentsMdPath = agentsMd;\n }\n\n // Also add built-in skills directory\n const builtInSkillsDir = resolve(dirname(import.meta.url.replace('file://', '')), '../skills/default');\n if (existsSync(builtInSkillsDir)) {\n onDemandDirs.push({ path: builtInSkillsDir, priority: 100 }); // Lowest priority\n allDirectories.push(builtInSkillsDir);\n }\n\n return {\n alwaysLoadedDirs,\n onDemandDirs,\n agentsMdPath,\n allDirectories,\n };\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 // Discover skill directories from standard locations\n const discovered = discoverSkillDirectories(resolvedWorkingDirectory);\n\n // Combine discovered directories with any additional configured directories\n const additionalDirs = (config.skills?.additionalDirectories || [])\n .map((dir) => resolve(configDir, dir))\n .filter((dir) => existsSync(dir));\n\n const resolvedSkillsDirectories = [\n ...discovered.allDirectories,\n ...additionalDirs,\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 // Resolve vector gateway config with env var overrides\n const resolvedVectorGateway: ResolvedVectorGatewayConfig = {\n redisUrl: process.env.VECTOR_REDIS_URL || config.vectorGateway?.redisUrl || null,\n httpUrl: process.env.VECTOR_HTTP_URL || config.vectorGateway?.httpUrl || null,\n embeddingModel:\n process.env.VECTOR_EMBEDDING_MODEL ||\n config.vectorGateway?.embeddingModel ||\n 'gemini-embedding-001',\n namespace: config.vectorGateway?.namespace || null,\n include: config.vectorGateway?.include || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.py',\n '**/*.go',\n '**/*.rs',\n '**/*.java',\n '**/*.md',\n '**/*.mdx',\n '**/*.txt',\n ],\n exclude: config.vectorGateway?.exclude || [\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/.git/**',\n '**/.next/**',\n '**/*.min.js',\n '**/*.bundle.js',\n '**/pnpm-lock.yaml',\n '**/package-lock.json',\n '**/yarn.lock',\n '**/.test-workspace/**',\n '**/.semantic-test-workspace/**',\n '**/.semantic-integration-test/**',\n ],\n };\n\n // Resolve remote server config with env var overrides\n // Default to production server when not explicitly configured\n const DEFAULT_REMOTE_URL = 'https://agent-remote-server.sparkecode.com';\n const remoteUrl = process.env.SPARKECODER_REMOTE_URL || config.remoteServer?.url || DEFAULT_REMOTE_URL;\n const remoteAuthKey = process.env.SPARKECODER_AUTH_KEY || config.remoteServer?.authKey || loadStoredAuthKey();\n \n const resolvedRemoteServer: ResolvedRemoteServerConfig = {\n url: remoteUrl,\n authKey: remoteAuthKey,\n isConfigured: !!remoteUrl && !!remoteAuthKey,\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 publicUrl: config.server.publicUrl,\n },\n resolvedWorkingDirectory,\n resolvedSkillsDirectories,\n resolvedDatabasePath,\n discoveredSkills: discovered,\n resolvedVectorGateway,\n resolvedRemoteServer,\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-6',\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// Auth Key Management (for remote server)\n// ============================================\n\nconst AUTH_KEY_FILE = 'auth-key.json';\n\ninterface StoredAuthKey {\n authKey: string;\n createdAt: string;\n userId?: string;\n}\n\n/**\n * Load stored auth key from app data directory\n */\nfunction loadStoredAuthKey(): string | null {\n const keysPath = join(getAppDataDirectory(), AUTH_KEY_FILE);\n if (!existsSync(keysPath)) {\n return null;\n }\n try {\n const content = readFileSync(keysPath, 'utf-8');\n const data = JSON.parse(content) as StoredAuthKey;\n return data.authKey || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Save auth key to app data directory\n */\nexport function saveAuthKey(authKey: string, userId?: string): void {\n const appDir = ensureAppDataDirectory();\n const keysPath = join(appDir, AUTH_KEY_FILE);\n const data: StoredAuthKey = {\n authKey,\n createdAt: new Date().toISOString(),\n userId,\n };\n writeFileSync(keysPath, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/**\n * Get stored auth key info\n */\nexport function getStoredAuthKeyInfo(): StoredAuthKey | null {\n const keysPath = join(getAppDataDirectory(), AUTH_KEY_FILE);\n if (!existsSync(keysPath)) {\n return null;\n }\n try {\n const content = readFileSync(keysPath, 'utf-8');\n return JSON.parse(content) as StoredAuthKey;\n } catch {\n return null;\n }\n}\n\n/**\n * Register with remote server and get new auth key\n */\nexport async function registerWithRemoteServer(\n serverUrl: string,\n name?: string\n): Promise<{ authKey: string; userId: string }> {\n const response = await fetch(`${serverUrl}/auth/register`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name: name || `CLI ${new Date().toISOString()}` }),\n });\n \n if (!response.ok) {\n const error = await response.json().catch(() => ({})) as { error?: string };\n throw new Error(error.error || `Failed to register: HTTP ${response.status}`);\n }\n \n const data = await response.json() as { authKey: string; userId: string };\n \n // Save the auth key\n saveAuthKey(data.authKey, data.userId);\n \n return data;\n}\n\n/**\n * Ensure we have a valid auth key for the remote server\n * If not configured, registers with the remote server to get one\n */\nexport async function ensureRemoteAuthKey(serverUrl: string): Promise<string> {\n // Check env var first\n if (process.env.SPARKECODER_AUTH_KEY) {\n return process.env.SPARKECODER_AUTH_KEY;\n }\n \n // Check stored key\n const storedKey = loadStoredAuthKey();\n if (storedKey) {\n return storedKey;\n }\n \n // Register with remote server\n const { authKey } = await registerWithRemoteServer(serverUrl);\n return authKey;\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 { 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\nTerminal output is stored in the global SparkECoder data directory. Use the \\`tail\\` option to read recent output.`,\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, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { nanoid } from 'nanoid';\nimport { getAppDataDirectory } from '../config/index.js';\n\nconst execAsync = promisify(exec);\n\n// Session prefix for all sparkecoder terminals\nconst SESSION_PREFIX = 'spark_';\n\n// Log directory base path - stored in global app data directory\nconst LOG_BASE_DIR = 'sessions';\n\nexport interface TerminalMeta {\n id: string;\n command: string;\n cwd: string;\n createdAt: string;\n sessionId: string;\n background: boolean;\n name?: string;\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 global terminal data directory\n * Uses OS-appropriate app data location (not the working directory)\n */\nfunction getTerminalDataDir(): string {\n const appDataDir = getAppDataDirectory();\n // Ensure directory exists\n if (!existsSync(appDataDir)) {\n mkdirSync(appDataDir, { recursive: true });\n }\n return appDataDir;\n}\n\n/**\n * Get the log directory for a terminal (stored in global app data, not working directory)\n */\nexport function getLogDir(terminalId: string, _workingDirectory: string, sessionId?: string): string {\n const baseDir = getTerminalDataDir();\n if (sessionId) {\n // Session-scoped path: ~/Library/Application Support/sparkecoder/sessions/{sessionId}/terminals/{terminalId}/\n return join(baseDir, LOG_BASE_DIR, sessionId, 'terminals', terminalId);\n }\n // Fallback for legacy terminals without sessionId\n return join(baseDir, '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; name?: 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 name: options.name,\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';\nimport * as LSP from '../lsp/index.js';\n\nexport interface WriteFileProgress {\n /** The file path being written */\n path: string;\n /** Relative path from working directory */\n relativePath: string;\n /** Write mode */\n mode: 'full' | 'str_replace';\n /** Status of the write operation */\n status: 'started' | 'content' | 'completed';\n /** For 'content' status: the content being written (may be chunked for streaming) */\n content?: string;\n /** When content is chunked, the chunk index (0-based) */\n chunkIndex?: number;\n /** When content is chunked, the total number of chunks */\n chunkCount?: number;\n /** When content is chunked, the start offset for this chunk */\n chunkStart?: number;\n /** Whether this content update is chunked */\n isChunked?: boolean;\n /** For str_replace: the old string being replaced */\n oldString?: string;\n /** For str_replace: the new string */\n newString?: string;\n /** Total content length (for progress tracking) */\n totalLength?: number;\n /** Action being performed */\n action?: 'created' | 'replaced' | 'edited';\n}\n\nexport interface WriteFileToolOptions {\n workingDirectory: string;\n sessionId: string;\n /** Enable LSP diagnostics after file edits (default: true) */\n enableLSP?: boolean;\n /** Called when write_file has progress to report (for streaming content) */\n onProgress?: (progress: WriteFileProgress) => void;\n}\n\nconst MAX_PROGRESS_CHUNK_SIZE = 16 * 1024;\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 const existed = existsSync(absolutePath);\n const action = existed ? 'replaced' : 'created';\n\n // Emit progress: started\n console.log('[WRITE-FILE] onProgress callback exists:', !!options.onProgress);\n console.log('[WRITE-FILE] Emitting started event for:', relativePath);\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'full',\n status: 'started',\n action,\n totalLength: content.length,\n });\n\n // Emit progress: content (chunked for large payloads to keep SSE stable)\n if (content.length <= MAX_PROGRESS_CHUNK_SIZE) {\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'full',\n status: 'content',\n content,\n action,\n totalLength: content.length,\n });\n } else {\n const chunkCount = Math.ceil(content.length / MAX_PROGRESS_CHUNK_SIZE);\n for (let i = 0; i < chunkCount; i += 1) {\n const chunkStart = i * MAX_PROGRESS_CHUNK_SIZE;\n const chunk = content.slice(chunkStart, chunkStart + MAX_PROGRESS_CHUNK_SIZE);\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'full',\n status: 'content',\n content: chunk,\n action,\n totalLength: content.length,\n chunkIndex: i,\n chunkCount,\n chunkStart,\n isChunked: true,\n });\n // Yield between chunks so SSE can flush progressively\n if (chunkCount > 1) {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\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 await writeFile(absolutePath, content, 'utf-8');\n\n // Get LSP diagnostics if enabled and file type is supported\n let diagnosticsOutput = '';\n if (options.enableLSP !== false && LSP.isSupported(absolutePath)) {\n await LSP.touchFile(absolutePath, true);\n diagnosticsOutput = await LSP.formatDiagnosticsOutput(absolutePath);\n }\n\n // Emit progress: completed\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'full',\n status: 'completed',\n action,\n totalLength: content.length,\n });\n\n return {\n success: true,\n path: absolutePath,\n relativePath,\n mode: 'full',\n action,\n bytesWritten: Buffer.byteLength(content, 'utf-8'),\n lineCount: content.split('\\n').length,\n ...(diagnosticsOutput && { diagnostics: diagnosticsOutput }),\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 // Emit progress: started\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'str_replace',\n status: 'started',\n action: 'edited',\n });\n\n // Emit progress: content (show the replacement)\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'str_replace',\n status: 'content',\n oldString: old_string,\n newString: new_string,\n action: 'edited',\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 // Get LSP diagnostics if enabled and file type is supported\n let diagnosticsOutput = '';\n if (options.enableLSP !== false && LSP.isSupported(absolutePath)) {\n await LSP.touchFile(absolutePath, true);\n diagnosticsOutput = await LSP.formatDiagnosticsOutput(absolutePath);\n }\n\n // Emit progress: completed\n options.onProgress?.({\n path: absolutePath,\n relativePath,\n mode: 'str_replace',\n status: 'completed',\n action: 'edited',\n });\n\n return {\n success: true,\n path: absolutePath,\n relativePath,\n mode: 'str_replace',\n linesRemoved: oldLines,\n linesAdded: newLines,\n lineDelta: newLines - oldLines,\n ...(diagnosticsOutput && { diagnostics: diagnosticsOutput }),\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 = await 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 (await 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 = await 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 = await 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 = await 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 = await 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 = await messageQueries.deleteFromSequence(sessionId, checkpoint.messageSequence);\n\n // Delete tool executions after the checkpoint was created\n await toolExecutionQueries.deleteAfterTime(sessionId, checkpoint.createdAt);\n\n // Delete checkpoints after this one (the file backups are deleted via CASCADE)\n const checkpointsDeleted = await 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 async function getCheckpoints(sessionId: string): Promise<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 = await 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 = await 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","/**\n * LSP Integration Module\n * \n * Provides Language Server Protocol support for the coding agent.\n * Automatically spawns LSP servers on-demand when files are touched,\n * collects diagnostics, and formats them for the agent.\n * \n * Usage:\n * import * as LSP from './lsp/index.js';\n * \n * // After editing a file, get diagnostics\n * await LSP.touchFile('/path/to/file.ts', true);\n * const diagnostics = await LSP.getDiagnostics('/path/to/file.ts');\n */\n\nimport { extname, dirname } from 'node:path';\nimport { getServerForExtension, getSupportedExtensions } from './servers.js';\nimport { createClient, normalizePath } from './client.js';\nimport {\n formatDiagnosticsForAgent,\n formatDiagnostic,\n DiagnosticSeverity,\n} from './types.js';\nimport type { Diagnostic, LSPClient } from './types.js';\n\n// Re-export types and utilities\nexport * from './types.js';\nexport { normalizePath } from './client.js';\nexport { getSupportedExtensions, getServerForExtension } from './servers.js';\n\n/**\n * Global state for LSP clients\n */\ninterface LSPState {\n clients: Map<string, LSPClient>; // key: `${serverId}:${root}`\n broken: Set<string>; // keys of servers that failed to start\n initialized: boolean;\n}\n\nlet state: LSPState = {\n clients: new Map(),\n broken: new Set(),\n initialized: false,\n};\n\n/**\n * Initialize the LSP system (optional, called automatically on first use)\n */\nexport async function init(): Promise<void> {\n if (state.initialized) return;\n state.initialized = true;\n}\n\n/**\n * Get or create an LSP client for a file\n */\nasync function getClientForFile(filePath: string): Promise<LSPClient | null> {\n const normalized = normalizePath(filePath);\n const ext = extname(normalized);\n \n // Check if we support this file type\n const serverDef = getServerForExtension(ext);\n if (!serverDef) {\n return null;\n }\n \n // Use file's directory as root (server will find project root)\n const root = dirname(normalized);\n const key = `${serverDef.id}:${root}`;\n \n // Check if we already have a client\n const existing = state.clients.get(key);\n if (existing) {\n return existing;\n }\n \n // Check if this server is broken for this root\n if (state.broken.has(key)) {\n return null;\n }\n \n // Spawn new server\n try {\n const handle = await serverDef.spawn(root);\n if (!handle) {\n state.broken.add(key);\n return null;\n }\n \n console.log(`[lsp] Started ${serverDef.name} for ${root}`);\n \n const client = await createClient(serverDef.id, handle, root);\n state.clients.set(key, client);\n \n // Handle process exit\n handle.process.on('exit', (code) => {\n console.log(`[lsp] ${serverDef.name} exited with code ${code}`);\n state.clients.delete(key);\n });\n \n return client;\n } catch (error) {\n console.error(`[lsp] Failed to start ${serverDef.name}:`, error);\n state.broken.add(key);\n return null;\n }\n}\n\n/**\n * Get all clients for a file (currently just TypeScript, but extensible)\n */\nasync function getClientsForFile(filePath: string): Promise<LSPClient[]> {\n const client = await getClientForFile(filePath);\n return client ? [client] : [];\n}\n\n/**\n * Touch a file (notify LSP of change and optionally wait for diagnostics)\n * \n * Call this after editing a file to get diagnostics.\n * \n * @param filePath - Path to the file\n * @param waitForDiagnostics - Whether to wait for diagnostics before returning\n * @returns Promise that resolves when done\n */\nexport async function touchFile(filePath: string, waitForDiagnostics = false): Promise<void> {\n const clients = await getClientsForFile(filePath);\n \n if (clients.length === 0) {\n return;\n }\n \n // Notify all clients\n await Promise.all(clients.map(client => client.notifyOpen(filePath)));\n \n // Optionally wait for diagnostics\n if (waitForDiagnostics) {\n await Promise.all(clients.map(client => client.waitForDiagnostics(filePath)));\n }\n}\n\n/**\n * Get diagnostics for a file\n */\nexport async function getDiagnostics(filePath: string): Promise<Diagnostic[]> {\n const normalized = normalizePath(filePath);\n const clients = await getClientsForFile(normalized);\n \n const allDiagnostics: Diagnostic[] = [];\n \n for (const client of clients) {\n const diags = client.getDiagnostics(normalized);\n allDiagnostics.push(...diags);\n }\n \n return allDiagnostics;\n}\n\n/**\n * Get all diagnostics from all clients\n */\nexport async function getAllDiagnostics(): Promise<Record<string, Diagnostic[]>> {\n const results: Record<string, Diagnostic[]> = {};\n \n for (const client of state.clients.values()) {\n const clientDiags = client.getAllDiagnostics();\n for (const [path, diagnostics] of clientDiags.entries()) {\n const existing = results[path] || [];\n existing.push(...diagnostics);\n results[path] = existing;\n }\n }\n \n return results;\n}\n\n/**\n * Wait for diagnostics on a file\n */\nexport async function waitForDiagnostics(filePath: string, timeoutMs = 5000): Promise<Diagnostic[]> {\n const normalized = normalizePath(filePath);\n const clients = await getClientsForFile(normalized);\n \n const allDiagnostics: Diagnostic[] = [];\n \n await Promise.all(\n clients.map(async (client) => {\n const diags = await client.waitForDiagnostics(normalized, timeoutMs);\n allDiagnostics.push(...diags);\n })\n );\n \n return allDiagnostics;\n}\n\n/**\n * Format diagnostics for agent output\n * \n * Call this after touchFile to get a formatted string to append to tool output.\n */\nexport async function formatDiagnosticsOutput(\n filePath: string,\n options: { maxDiagnostics?: number; errorsOnly?: boolean } = {}\n): Promise<string> {\n const diagnostics = await getDiagnostics(filePath);\n return formatDiagnosticsForAgent(filePath, diagnostics, options);\n}\n\n/**\n * Get errors only (severity = 1)\n */\nexport function getErrors(diagnostics: Diagnostic[]): Diagnostic[] {\n return diagnostics.filter(d => d.severity === DiagnosticSeverity.Error);\n}\n\n/**\n * Check if a file type is supported\n */\nexport function isSupported(filePath: string): boolean {\n const ext = extname(filePath);\n return getServerForExtension(ext) !== null;\n}\n\n/**\n * Shutdown all LSP clients\n */\nexport async function shutdown(): Promise<void> {\n const shutdownPromises: Promise<void>[] = [];\n \n for (const [key, client] of state.clients.entries()) {\n console.log(`[lsp] Shutting down ${key}`);\n shutdownPromises.push(client.shutdown());\n }\n \n await Promise.allSettled(shutdownPromises);\n \n state.clients.clear();\n state.broken.clear();\n state.initialized = false;\n}\n\n/**\n * Reset state (for testing)\n */\nexport function reset(): void {\n state = {\n clients: new Map(),\n broken: new Set(),\n initialized: false,\n };\n}\n\n// Utility exports for direct usage\nexport const DiagnosticUtils = {\n format: formatDiagnostic,\n formatForAgent: formatDiagnosticsForAgent,\n Severity: DiagnosticSeverity,\n};\n\n// Alias for backwards compatibility\nexport { DiagnosticUtils as Diagnostic };\n","/**\n * LSP Server Definitions\n * \n * Defines how to spawn and configure various LSP servers.\n * Currently supports TypeScript/JavaScript with typescript-language-server.\n */\n\nimport { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport type { LSPServerDefinition, LSPServerHandle } from './types.js';\n\n/**\n * Find the nearest directory containing one of the given files\n */\nfunction findNearestRoot(startDir: string, markers: string[]): string | null {\n let dir = startDir;\n const root = '/';\n \n while (dir !== root) {\n for (const marker of markers) {\n if (existsSync(resolve(dir, marker))) {\n return dir;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n \n return null;\n}\n\n/**\n * Check if a command exists in PATH\n */\nasync function commandExists(cmd: string): Promise<boolean> {\n try {\n const { exec } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execAsync = promisify(exec);\n \n const isWindows = process.platform === 'win32';\n const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;\n \n await execAsync(checkCmd);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * TypeScript/JavaScript Language Server\n * \n * Uses typescript-language-server which wraps tsserver.\n * Provides type checking, error detection, and more.\n */\nexport const TypeScriptServer: LSPServerDefinition = {\n id: 'typescript',\n name: 'TypeScript Language Server',\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'],\n \n async spawn(root: string): Promise<LSPServerHandle | null> {\n // Check for package manager lock files to determine project root\n const projectRoot = findNearestRoot(root, [\n 'package-lock.json',\n 'pnpm-lock.yaml',\n 'yarn.lock',\n 'bun.lockb',\n 'bun.lock',\n ]) || root;\n \n // Try to find typescript-language-server\n const hasNpx = await commandExists('npx');\n const hasBunx = await commandExists('bunx');\n const hasPnpx = await commandExists('pnpx');\n \n let cmd: string[];\n \n if (hasPnpx) {\n cmd = ['pnpx', 'typescript-language-server', '--stdio'];\n } else if (hasBunx) {\n cmd = ['bunx', 'typescript-language-server', '--stdio'];\n } else if (hasNpx) {\n cmd = ['npx', 'typescript-language-server', '--stdio'];\n } else {\n console.warn('[lsp] No package runner (npx/bunx/pnpx) found for typescript-language-server');\n return null;\n }\n \n try {\n const proc = spawn(cmd[0], cmd.slice(1), {\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: {\n ...process.env,\n // Suppress some noisy output\n TSS_LOG: '-level none',\n },\n });\n \n // Handle stderr (for debugging)\n proc.stderr?.on('data', (data) => {\n const msg = data.toString().trim();\n if (msg && !msg.includes('deprecated')) {\n // Only log non-trivial errors\n console.debug('[lsp:typescript:stderr]', msg);\n }\n });\n \n return {\n process: proc,\n initialization: {\n // TypeScript-specific initialization options\n preferences: {\n includeInlayParameterNameHints: 'none',\n includeInlayPropertyDeclarationTypeHints: false,\n includeInlayFunctionLikeReturnTypeHints: false,\n },\n },\n };\n } catch (error) {\n console.error('[lsp] Failed to spawn typescript-language-server:', error);\n return null;\n }\n },\n};\n\n/**\n * All available LSP servers\n */\nexport const servers: LSPServerDefinition[] = [\n TypeScriptServer,\n];\n\n/**\n * Get the appropriate server for a file extension\n */\nexport function getServerForExtension(ext: string): LSPServerDefinition | null {\n for (const server of servers) {\n if (server.extensions.includes(ext)) {\n return server;\n }\n }\n return null;\n}\n\n/**\n * Get all supported file extensions\n */\nexport function getSupportedExtensions(): string[] {\n const extensions = new Set<string>();\n for (const server of servers) {\n for (const ext of server.extensions) {\n extensions.add(ext);\n }\n }\n return Array.from(extensions);\n}\n","/**\n * LSP Client\n * \n * Manages communication with an LSP server via JSON-RPC over stdio.\n * Handles initialization, file notifications, and diagnostics collection.\n */\n\nimport {\n createMessageConnection,\n StreamMessageReader,\n StreamMessageWriter,\n type MessageConnection,\n} from 'vscode-jsonrpc/node.js';\nimport { pathToFileURL, fileURLToPath } from 'node:url';\nimport { readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { extname, normalize } from 'node:path';\nimport type { LSPClient, LSPServerHandle, Diagnostic } from './types.js';\n\n/**\n * Map file extension to LSP language ID\n */\nfunction getLanguageId(filePath: string): string {\n const ext = extname(filePath).toLowerCase();\n const map: Record<string, string> = {\n '.ts': 'typescript',\n '.tsx': 'typescriptreact',\n '.js': 'javascript',\n '.jsx': 'javascriptreact',\n '.mjs': 'javascript',\n '.cjs': 'javascript',\n '.mts': 'typescript',\n '.cts': 'typescript',\n '.json': 'json',\n '.jsonc': 'jsonc',\n };\n return map[ext] || 'plaintext';\n}\n\n/**\n * Normalize a file path for consistent key usage\n */\nexport function normalizePath(filePath: string): string {\n return normalize(filePath);\n}\n\n/**\n * Create an LSP client connected to a server\n */\nexport async function createClient(\n serverId: string,\n handle: LSPServerHandle,\n root: string\n): Promise<LSPClient> {\n const { process: proc } = handle;\n \n if (!proc.stdout || !proc.stdin) {\n throw new Error('LSP server process has no stdout/stdin');\n }\n \n // Create JSON-RPC connection over stdio\n const connection: MessageConnection = createMessageConnection(\n new StreamMessageReader(proc.stdout),\n new StreamMessageWriter(proc.stdin)\n );\n \n // Diagnostics storage\n const diagnostics = new Map<string, Diagnostic[]>();\n \n // Track open files and their versions\n const fileVersions = new Map<string, number>();\n \n // Event listeners for diagnostics updates\n const diagnosticListeners = new Map<string, Array<() => void>>();\n \n // Listen for diagnostic notifications\n connection.onNotification('textDocument/publishDiagnostics', (params: any) => {\n const filePath = normalizePath(fileURLToPath(params.uri));\n diagnostics.set(filePath, params.diagnostics || []);\n \n // Notify any waiters\n const listeners = diagnosticListeners.get(filePath);\n if (listeners) {\n for (const listener of listeners) {\n listener();\n }\n }\n });\n \n // Handle server requests\n connection.onRequest('workspace/configuration', async (params: any) => {\n // Return configuration for each requested section\n return params.items.map(() => handle.initialization || {});\n });\n \n connection.onRequest('client/registerCapability', async () => {\n // Accept capability registration\n return null;\n });\n \n connection.onRequest('window/workDoneProgress/create', async () => {\n // Accept progress token creation\n return null;\n });\n \n connection.onNotification('window/logMessage', (params: any) => {\n // Optionally log server messages\n if (params.type <= 2) { // Error or Warning\n console.debug(`[lsp:${serverId}]`, params.message);\n }\n });\n \n // Start listening\n connection.listen();\n \n // Initialize the server\n const initResult = await connection.sendRequest('initialize', {\n processId: process.pid,\n rootUri: pathToFileURL(root).href,\n rootPath: root,\n workspaceFolders: [\n {\n name: 'workspace',\n uri: pathToFileURL(root).href,\n },\n ],\n capabilities: {\n textDocument: {\n synchronization: {\n dynamicRegistration: true,\n willSave: false,\n willSaveWaitUntil: false,\n didSave: true,\n },\n publishDiagnostics: {\n relatedInformation: true,\n versionSupport: true,\n codeDescriptionSupport: true,\n },\n completion: {\n dynamicRegistration: true,\n completionItem: {\n snippetSupport: true,\n documentationFormat: ['markdown', 'plaintext'],\n },\n },\n hover: {\n dynamicRegistration: true,\n contentFormat: ['markdown', 'plaintext'],\n },\n definition: {\n dynamicRegistration: true,\n },\n references: {\n dynamicRegistration: true,\n },\n documentSymbol: {\n dynamicRegistration: true,\n },\n },\n workspace: {\n configuration: true,\n didChangeConfiguration: {\n dynamicRegistration: true,\n },\n didChangeWatchedFiles: {\n dynamicRegistration: true,\n },\n workspaceFolders: true,\n },\n },\n initializationOptions: handle.initialization,\n });\n \n // Send initialized notification\n await connection.sendNotification('initialized', {});\n \n // Return client interface\n const client: LSPClient = {\n serverId,\n root,\n diagnostics,\n \n async notifyOpen(filePath: string): Promise<void> {\n const normalized = normalizePath(filePath);\n \n if (!existsSync(normalized)) {\n return;\n }\n \n try {\n const content = await readFile(normalized, 'utf-8');\n const version = (fileVersions.get(normalized) ?? -1) + 1;\n fileVersions.set(normalized, version);\n \n if (version === 0) {\n // First time opening\n await connection.sendNotification('textDocument/didOpen', {\n textDocument: {\n uri: pathToFileURL(normalized).href,\n languageId: getLanguageId(normalized),\n version,\n text: content,\n },\n });\n } else {\n // Already open, send change\n await connection.sendNotification('textDocument/didChange', {\n textDocument: {\n uri: pathToFileURL(normalized).href,\n version,\n },\n contentChanges: [{ text: content }],\n });\n }\n } catch (error) {\n console.error('[lsp] Error notifying open:', error);\n }\n },\n \n async notifyChange(filePath: string): Promise<void> {\n const normalized = normalizePath(filePath);\n \n if (!existsSync(normalized)) {\n return;\n }\n \n try {\n const content = await readFile(normalized, 'utf-8');\n const version = (fileVersions.get(normalized) ?? 0) + 1;\n fileVersions.set(normalized, version);\n \n await connection.sendNotification('textDocument/didChange', {\n textDocument: {\n uri: pathToFileURL(normalized).href,\n version,\n },\n contentChanges: [{ text: content }],\n });\n } catch (error) {\n console.error('[lsp] Error notifying change:', error);\n }\n },\n \n async notifyClose(filePath: string): Promise<void> {\n const normalized = normalizePath(filePath);\n fileVersions.delete(normalized);\n diagnostics.delete(normalized);\n \n try {\n await connection.sendNotification('textDocument/didClose', {\n textDocument: {\n uri: pathToFileURL(normalized).href,\n },\n });\n } catch (error) {\n console.error('[lsp] Error notifying close:', error);\n }\n },\n \n async notifyWatchedFilesChanged(changes: Array<{ uri: string; type: number }>): Promise<void> {\n try {\n await connection.sendNotification('workspace/didChangeWatchedFiles', {\n changes,\n });\n } catch (error) {\n console.error('[lsp] Error notifying watched files:', error);\n }\n },\n \n async waitForDiagnostics(filePath: string, timeoutMs = 5000): Promise<Diagnostic[]> {\n const normalized = normalizePath(filePath);\n \n return new Promise<Diagnostic[]>((resolve) => {\n const startTime = Date.now();\n let debounceTimer: NodeJS.Timeout | undefined;\n let resolved = false;\n \n const cleanup = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n const listeners = diagnosticListeners.get(normalized);\n if (listeners) {\n const idx = listeners.indexOf(onDiagnostic);\n if (idx >= 0) listeners.splice(idx, 1);\n if (listeners.length === 0) {\n diagnosticListeners.delete(normalized);\n }\n }\n };\n \n const finish = () => {\n if (resolved) return;\n resolved = true;\n cleanup();\n resolve(diagnostics.get(normalized) || []);\n };\n \n const onDiagnostic = () => {\n // Debounce: wait 150ms after last update\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(finish, 150);\n };\n \n // Register listener\n if (!diagnosticListeners.has(normalized)) {\n diagnosticListeners.set(normalized, []);\n }\n diagnosticListeners.get(normalized)!.push(onDiagnostic);\n \n // Timeout fallback\n setTimeout(() => {\n if (!resolved) {\n finish();\n }\n }, timeoutMs);\n \n // If we already have diagnostics, trigger debounce\n if (diagnostics.has(normalized)) {\n onDiagnostic();\n }\n });\n },\n \n getDiagnostics(filePath: string): Diagnostic[] {\n return diagnostics.get(normalizePath(filePath)) || [];\n },\n \n getAllDiagnostics(): Map<string, Diagnostic[]> {\n return new Map(diagnostics);\n },\n \n async shutdown(): Promise<void> {\n try {\n await connection.sendRequest('shutdown');\n await connection.sendNotification('exit');\n connection.end();\n connection.dispose();\n proc.kill();\n } catch (error) {\n // Force kill if graceful shutdown fails\n proc.kill('SIGKILL');\n }\n },\n };\n \n return client;\n}\n","/**\n * LSP Types\n * \n * Type definitions for the Language Server Protocol integration.\n * These types are compatible with vscode-languageserver-types.\n */\n\n/**\n * Diagnostic severity levels from LSP spec\n */\nexport enum DiagnosticSeverity {\n Error = 1,\n Warning = 2,\n Information = 3,\n Hint = 4,\n}\n\n/**\n * Position in a text document (0-indexed)\n */\nexport interface Position {\n line: number;\n character: number;\n}\n\n/**\n * Range in a text document\n */\nexport interface Range {\n start: Position;\n end: Position;\n}\n\n/**\n * A diagnostic message from an LSP server\n */\nexport interface Diagnostic {\n range: Range;\n message: string;\n severity?: DiagnosticSeverity;\n code?: number | string;\n source?: string;\n relatedInformation?: DiagnosticRelatedInformation[];\n}\n\n/**\n * Related information for a diagnostic\n */\nexport interface DiagnosticRelatedInformation {\n location: {\n uri: string;\n range: Range;\n };\n message: string;\n}\n\n/**\n * Parameters for textDocument/publishDiagnostics notification\n */\nexport interface PublishDiagnosticsParams {\n uri: string;\n version?: number;\n diagnostics: Diagnostic[];\n}\n\n/**\n * LSP Server handle (spawned process)\n */\nexport interface LSPServerHandle {\n process: import('node:child_process').ChildProcess;\n initialization?: Record<string, unknown>;\n}\n\n/**\n * LSP Server definition\n */\nexport interface LSPServerDefinition {\n id: string;\n name: string;\n extensions: string[];\n spawn: (root: string) => Promise<LSPServerHandle | null>;\n}\n\n/**\n * LSP Client interface\n */\nexport interface LSPClient {\n serverId: string;\n root: string;\n diagnostics: Map<string, Diagnostic[]>;\n \n notifyOpen(filePath: string): Promise<void>;\n notifyChange(filePath: string): Promise<void>;\n notifyClose(filePath: string): Promise<void>;\n notifyWatchedFilesChanged(changes: Array<{ uri: string; type: number }>): Promise<void>;\n \n waitForDiagnostics(filePath: string, timeoutMs?: number): Promise<Diagnostic[]>;\n getDiagnostics(filePath: string): Diagnostic[];\n getAllDiagnostics(): Map<string, Diagnostic[]>;\n \n shutdown(): Promise<void>;\n}\n\n/**\n * Format a diagnostic for display to the agent\n */\nexport function formatDiagnostic(diagnostic: Diagnostic): string {\n const severity = {\n [DiagnosticSeverity.Error]: 'ERROR',\n [DiagnosticSeverity.Warning]: 'WARN',\n [DiagnosticSeverity.Information]: 'INFO',\n [DiagnosticSeverity.Hint]: 'HINT',\n }[diagnostic.severity ?? DiagnosticSeverity.Error];\n \n const line = diagnostic.range.start.line + 1; // Convert to 1-indexed\n const col = diagnostic.range.start.character + 1;\n const source = diagnostic.source ? ` [${diagnostic.source}]` : '';\n \n return `${severity} [${line}:${col}]${source} ${diagnostic.message}`;\n}\n\n/**\n * Format diagnostics for agent output\n */\nexport function formatDiagnosticsForAgent(\n filePath: string,\n diagnostics: Diagnostic[],\n options: { maxDiagnostics?: number; errorsOnly?: boolean } = {}\n): string {\n const { maxDiagnostics = 20, errorsOnly = true } = options;\n \n // Filter to errors only if requested\n const filtered = errorsOnly\n ? diagnostics.filter(d => d.severity === DiagnosticSeverity.Error)\n : diagnostics;\n \n if (filtered.length === 0) return '';\n \n const limited = filtered.slice(0, maxDiagnostics);\n const suffix = filtered.length > maxDiagnostics\n ? `\\n... and ${filtered.length - maxDiagnostics} more`\n : '';\n \n const formatted = limited.map(formatDiagnostic).join('\\n');\n \n return `\\n\\nLSP errors detected in this file, please fix:\\n<diagnostics file=\"${filePath}\">\\n${formatted}${suffix}\\n</diagnostics>`;\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 = await 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 = await todoQueries.getBySession(options.sessionId);\n \n const stats = {\n total: todos.length,\n pending: todos.filter((t: TodoItem) => t.status === 'pending').length,\n inProgress: todos.filter((t: TodoItem) => t.status === 'in_progress').length,\n completed: todos.filter((t: TodoItem) => t.status === 'completed').length,\n cancelled: todos.filter((t: TodoItem) => 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 = await 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 = await 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 (await 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 await 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","/**\n * Linter Tool\n * \n * Provides the agent with the ability to check files for lint/type errors\n * using the LSP (Language Server Protocol) integration.\n */\n\nimport { tool } from 'ai';\nimport { z } from 'zod';\nimport { resolve, relative, isAbsolute, extname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { readdir, stat } from 'node:fs/promises';\nimport * as LSP from '../lsp/index.js';\nimport type { Diagnostic } from '../lsp/types.js';\n\nexport interface LinterToolOptions {\n workingDirectory: string;\n}\n\nconst linterInputSchema = z.object({\n paths: z\n .array(z.string())\n .optional()\n .describe('File or directory paths to check for lint errors. If not provided, returns diagnostics for all recently touched files.'),\n fix: z\n .boolean()\n .optional()\n .default(false)\n .describe('Reserved for future use: auto-fix lint errors (not yet implemented)'),\n});\n\n/**\n * Recursively find all supported files in a directory\n */\nasync function findSupportedFiles(\n dir: string,\n workingDirectory: string,\n maxFiles = 50\n): Promise<string[]> {\n const files: string[] = [];\n const supportedExtensions = LSP.getSupportedExtensions();\n\n async function walk(currentDir: string) {\n if (files.length >= maxFiles) return;\n\n try {\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (files.length >= maxFiles) break;\n\n const fullPath = resolve(currentDir, entry.name);\n\n // Skip node_modules, .git, and other common ignore patterns\n if (entry.isDirectory()) {\n if (['node_modules', '.git', 'dist', 'build', '.next', 'coverage'].includes(entry.name)) {\n continue;\n }\n await walk(fullPath);\n } else if (entry.isFile()) {\n const ext = extname(entry.name);\n if (supportedExtensions.includes(ext)) {\n files.push(fullPath);\n }\n }\n }\n } catch {\n // Ignore permission errors etc.\n }\n }\n\n await walk(dir);\n return files;\n}\n\nexport function createLinterTool(options: LinterToolOptions) {\n return tool({\n description: `Check files for linting and type errors using the Language Server Protocol (LSP).\n\nSupports TypeScript, JavaScript, TSX, JSX files.\n\nUsage:\n- \\`linter({})\\` - Get diagnostics for all recently edited files\n- \\`linter({ paths: [\"src/app.ts\"] })\\` - Check specific files\n- \\`linter({ paths: [\"src/\"] })\\` - Check all supported files in a directory\n\nReturns detailed error information including line numbers, error messages, and severity.\nUse this after making changes to verify your code is correct, or proactively to find issues.\n\nWorking directory: ${options.workingDirectory}`,\n\n inputSchema: linterInputSchema,\n\n execute: async ({ paths }: z.infer<typeof linterInputSchema>) => {\n try {\n // If no paths provided, get all diagnostics from LSP\n if (!paths || paths.length === 0) {\n const allDiagnostics = await LSP.getAllDiagnostics();\n \n if (Object.keys(allDiagnostics).length === 0) {\n return {\n success: true,\n message: 'No lint errors found. No files have been analyzed yet - specify paths to check specific files.',\n files: [],\n totalErrors: 0,\n totalWarnings: 0,\n };\n }\n\n return formatDiagnosticsResult(allDiagnostics, options.workingDirectory);\n }\n\n // Process provided paths\n const filesToCheck: string[] = [];\n\n for (const path of paths) {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(options.workingDirectory, path);\n\n if (!existsSync(absolutePath)) {\n continue;\n }\n\n const stats = await stat(absolutePath);\n\n if (stats.isDirectory()) {\n const dirFiles = await findSupportedFiles(absolutePath, options.workingDirectory);\n filesToCheck.push(...dirFiles);\n } else if (stats.isFile()) {\n if (LSP.isSupported(absolutePath)) {\n filesToCheck.push(absolutePath);\n }\n }\n }\n\n if (filesToCheck.length === 0) {\n return {\n success: true,\n message: 'No supported files found to check. Supported extensions: ' + LSP.getSupportedExtensions().join(', '),\n files: [],\n totalErrors: 0,\n totalWarnings: 0,\n };\n }\n\n // Touch all files and wait for diagnostics\n await Promise.all(\n filesToCheck.map(file => LSP.touchFile(file, true))\n );\n\n // Collect diagnostics for all files\n const diagnosticsMap: Record<string, Diagnostic[]> = {};\n\n for (const file of filesToCheck) {\n const diagnostics = await LSP.getDiagnostics(file);\n if (diagnostics.length > 0) {\n diagnosticsMap[file] = diagnostics;\n }\n }\n\n return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\n/**\n * Format diagnostics into a structured result\n */\nfunction formatDiagnosticsResult(\n diagnosticsMap: Record<string, Diagnostic[]>,\n workingDirectory: string\n) {\n let totalErrors = 0;\n let totalWarnings = 0;\n let totalInfo = 0;\n\n const files: Array<{\n path: string;\n relativePath: string;\n errors: number;\n warnings: number;\n diagnostics: Array<{\n severity: string;\n line: number;\n column: number;\n message: string;\n source?: string;\n code?: string | number;\n }>;\n }> = [];\n\n for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {\n const relativePath = relative(workingDirectory, filePath);\n let fileErrors = 0;\n let fileWarnings = 0;\n\n const formattedDiagnostics = diagnostics.map(d => {\n const severity = getSeverityString(d.severity);\n \n if (d.severity === LSP.DiagnosticSeverity.Error) {\n fileErrors++;\n totalErrors++;\n } else if (d.severity === LSP.DiagnosticSeverity.Warning) {\n fileWarnings++;\n totalWarnings++;\n } else {\n totalInfo++;\n }\n\n return {\n severity,\n line: d.range.start.line + 1,\n column: d.range.start.character + 1,\n message: d.message,\n source: d.source,\n code: d.code,\n };\n });\n\n files.push({\n path: filePath,\n relativePath,\n errors: fileErrors,\n warnings: fileWarnings,\n diagnostics: formattedDiagnostics,\n });\n }\n\n // Sort by errors (most first)\n files.sort((a, b) => b.errors - a.errors);\n\n const hasIssues = totalErrors > 0 || totalWarnings > 0;\n\n return {\n success: true,\n message: hasIssues\n ? `Found ${totalErrors} error(s) and ${totalWarnings} warning(s) in ${files.length} file(s).`\n : `No lint errors found in ${Object.keys(diagnosticsMap).length || 'any'} file(s).`,\n files,\n totalErrors,\n totalWarnings,\n totalInfo,\n summary: hasIssues\n ? formatSummary(files)\n : undefined,\n };\n}\n\n/**\n * Get severity as a string\n */\nfunction getSeverityString(severity?: number): string {\n switch (severity) {\n case LSP.DiagnosticSeverity.Error:\n return 'error';\n case LSP.DiagnosticSeverity.Warning:\n return 'warning';\n case LSP.DiagnosticSeverity.Information:\n return 'info';\n case LSP.DiagnosticSeverity.Hint:\n return 'hint';\n default:\n return 'error';\n }\n}\n\n/**\n * Format a human-readable summary\n */\nfunction formatSummary(\n files: Array<{\n relativePath: string;\n diagnostics: Array<{\n severity: string;\n line: number;\n column: number;\n message: string;\n }>;\n }>\n): string {\n const lines: string[] = [];\n\n for (const file of files) {\n lines.push(`\\n${file.relativePath}:`);\n for (const d of file.diagnostics.slice(0, 10)) {\n const prefix = d.severity === 'error' ? '❌' : d.severity === 'warning' ? '⚠️' : 'ℹ️';\n lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);\n }\n if (file.diagnostics.length > 10) {\n lines.push(` ... and ${file.diagnostics.length - 10} more`);\n }\n }\n\n return lines.join('\\n');\n}\n\nexport type LinterTool = ReturnType<typeof createLinterTool>;\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport { createSearchSubagent, SearchResult, SubagentProgressEvent } from '../agent/subagents/index.js';\nimport { truncateOutput } from '../utils/truncate.js';\n\nconst MAX_RESULT_CHARS = 8_000;\n\n/**\n * Progress event for the explore_agent tool (emitted via onProgress callback)\n */\nexport interface SearchToolProgress {\n status: 'started' | 'step' | 'complete' | 'error';\n subagentId?: string;\n stepType?: 'thought' | 'tool_call' | 'tool_result' | 'text';\n stepContent?: string;\n toolName?: string;\n toolInput?: unknown;\n toolOutput?: unknown;\n result?: SearchResult;\n error?: string;\n}\n\nexport interface SearchToolOptions {\n sessionId: string;\n workingDirectory: string;\n /** Callback for progress updates (for streaming to UI) */\n onProgress?: (progress: SearchToolProgress) => void | Promise<void>;\n}\n\n/**\n * Create the explore_agent tool that spawns a SearchSubagent\n * \n * This tool allows the main agent to delegate explore tasks to a specialized\n * mini-agent that uses a smaller, faster model (Gemini 3 Flash Preview).\n * \n * The subagent has access to:\n * - grep: Search for patterns in files\n * - glob: Find files by pattern\n * - read_file: Read file contents\n * - list_dir: List directory contents\n * \n * Progress is streamed back to the UI so users can see exploration happening.\n */\nexport function createSearchTool(options: SearchToolOptions) {\n return tool({\n description: `Delegate an explore task to the explore_agent tool. Use this when you need to:\n- Find files or code matching a pattern\n- Explore the codebase structure\n- Search for specific functions, classes, or variables\n- Understand how a feature is implemented\n\nThe Explore agent will explore the codebase and return a summary of findings.\nThis is more thorough than a simple grep because it can follow references and understand context.\n\nExamples:\n- \"Find all React components that use the useState hook\"\n- \"Where is the authentication logic implemented?\"\n- \"Find all API routes and their handlers\"\n- \"Search for usages of the UserService class\"`,\n\n inputSchema: z.object({\n query: z.string().describe('What to search for. Be specific about what you\\'re looking for.'),\n context: z.string().optional().describe('Optional additional context about why you need this information.'),\n }),\n\n execute: async ({ query, context }, toolOptions) => {\n const toolCallId = (toolOptions as any).toolCallId || `explore_agent_${Date.now()}`;\n \n // Emit started event\n await options.onProgress?.({\n status: 'started',\n subagentId: toolCallId,\n });\n \n try {\n const subagent = createSearchSubagent();\n \n // Build the full task\n const fullTask = context \n ? `${query}\\n\\nContext: ${context}`\n : query;\n \n // Run the subagent\n const result = await subagent.run({\n task: fullTask,\n sessionId: options.sessionId,\n toolCallId,\n workingDirectory: options.workingDirectory,\n onProgress: async (event: SubagentProgressEvent) => {\n // Map subagent events to explore_agent tool progress\n if (event.type === 'step' && event.step) {\n await options.onProgress?.({\n status: 'step',\n subagentId: event.subagentId,\n stepType: event.step.type,\n stepContent: event.step.content,\n toolName: event.step.toolName,\n toolInput: event.step.toolInput,\n toolOutput: event.step.toolOutput,\n });\n } else if (event.type === 'complete') {\n await options.onProgress?.({\n status: 'complete',\n subagentId: event.subagentId,\n result: event.result as SearchResult,\n });\n } else if (event.type === 'error') {\n await options.onProgress?.({\n status: 'error',\n subagentId: event.subagentId,\n error: event.error,\n });\n }\n },\n });\n \n if (!result.success) {\n return {\n success: false,\n error: result.error || 'Search failed',\n executionId: result.executionId,\n };\n }\n \n const searchResult = result.result!;\n \n // Format the result for the main agent\n let formattedResult = `## Explore Agent Results\\n\\n`;\n formattedResult += `**Summary:** ${searchResult.summary}\\n\\n`;\n \n if (searchResult.findings.length > 0) {\n formattedResult += `### Key Findings (${searchResult.findings.length} items)\\n\\n`;\n \n for (const finding of searchResult.findings) {\n if (finding.type === 'match') {\n formattedResult += `- **${finding.path}:${finding.lineNumber}** - ${truncateOutput(finding.content || '', 200)}\\n`;\n } else if (finding.type === 'file') {\n formattedResult += `- **${finding.path}** ${finding.context ? `(${finding.context})` : ''}\\n`;\n }\n }\n }\n \n formattedResult += `\\n**Stats:** ${searchResult.matchCount} matches across ${searchResult.filesSearched} files searched`;\n \n return {\n success: true,\n query: searchResult.query,\n summary: searchResult.summary,\n findings: searchResult.findings,\n matchCount: searchResult.matchCount,\n filesSearched: searchResult.filesSearched,\n formattedResult: truncateOutput(formattedResult, MAX_RESULT_CHARS),\n executionId: result.executionId,\n stepsCount: result.steps.length,\n };\n } catch (error: any) {\n await options.onProgress?.({\n status: 'error',\n error: error.message,\n });\n \n return {\n success: false,\n error: error.message,\n };\n }\n },\n });\n}\n\nexport type SearchTool = ReturnType<typeof createSearchTool>;\n","import {\n streamText,\n generateText,\n stepCountIs,\n type ToolSet,\n} from 'ai';\nimport { nanoid } from 'nanoid';\nimport { resolveModel, SUBAGENT_MODELS } from './model.js';\nimport { subagentQueries, SubagentExecution, SubagentStep } from '../db/index.js';\n\n/**\n * Progress event emitted by subagents\n */\nexport interface SubagentProgressEvent {\n type: 'step' | 'text' | 'tool_call' | 'tool_result' | 'complete' | 'error';\n subagentId: string;\n subagentType: string;\n step?: SubagentStep;\n text?: string;\n toolName?: string;\n toolInput?: unknown;\n toolOutput?: unknown;\n result?: unknown;\n error?: string;\n}\n\n/**\n * Options for running a subagent\n */\nexport interface SubagentRunOptions {\n task: string;\n sessionId: string;\n toolCallId: string;\n workingDirectory: string;\n /** Callback for progress events */\n onProgress?: (event: SubagentProgressEvent) => void | Promise<void>;\n /** Abort signal */\n abortSignal?: AbortSignal;\n}\n\n/**\n * Result from a subagent execution\n */\nexport interface SubagentResult<T = unknown> {\n success: boolean;\n result?: T;\n error?: string;\n steps: SubagentStep[];\n executionId: string;\n}\n\n/**\n * Base class for subagents.\n * \n * Subagents are lightweight agents that perform specific tasks using smaller,\n * faster models. They're spawned by the main agent via tools and report progress\n * back to the UI.\n * \n * To create a new subagent type:\n * 1. Extend this class\n * 2. Implement `getTools()` to return the tools available to this subagent\n * 3. Implement `getSystemPrompt()` to return the system prompt\n * 4. Optionally override `parseResult()` to structure the final output\n */\nexport abstract class Subagent<TResult = unknown> {\n /** Unique identifier for this subagent type */\n abstract readonly type: string;\n \n /** Human-readable name for this subagent */\n abstract readonly name: string;\n \n /** Model to use (defaults to gemini-3-flash-preview) */\n protected model: string;\n \n /** Maximum steps before stopping */\n protected maxSteps: number = 20;\n \n constructor(model?: string) {\n this.model = model || SUBAGENT_MODELS.default;\n }\n \n /**\n * Get the tools available to this subagent\n */\n protected abstract getTools(options: SubagentRunOptions): ToolSet;\n \n /**\n * Get the system prompt for this subagent\n */\n protected abstract getSystemPrompt(options: SubagentRunOptions): string;\n \n /**\n * Parse the final result from the subagent's output.\n * Override this to structure the result for your subagent type.\n */\n protected parseResult(text: string, steps: SubagentStep[]): TResult {\n return { text, steps } as TResult;\n }\n \n /**\n * Run the subagent with streaming progress updates\n */\n async run(options: SubagentRunOptions): Promise<SubagentResult<TResult>> {\n const { task, sessionId, toolCallId, onProgress, abortSignal } = options;\n const steps: SubagentStep[] = [];\n \n // Create execution record in database\n const execution = await subagentQueries.create({\n sessionId,\n toolCallId,\n subagentType: this.type,\n task,\n model: this.model,\n });\n \n const addStep = async (step: Omit<SubagentStep, 'id' | 'timestamp'>) => {\n const fullStep: SubagentStep = {\n id: nanoid(8),\n timestamp: Date.now(),\n ...step,\n };\n steps.push(fullStep);\n \n // Update database\n await subagentQueries.addStep(execution.id, fullStep);\n \n // Emit progress\n await onProgress?.({\n type: 'step',\n subagentId: execution.id,\n subagentType: this.type,\n step: fullStep,\n });\n };\n \n try {\n const tools = this.getTools(options);\n const systemPrompt = this.getSystemPrompt(options);\n \n // Run the subagent\n const result = await generateText({\n model: resolveModel(this.model) as any,\n system: systemPrompt,\n messages: [\n { role: 'user', content: task }\n ],\n tools,\n stopWhen: stepCountIs(this.maxSteps),\n abortSignal,\n onStepFinish: async (step) => {\n // Record text output\n if (step.text) {\n await addStep({\n type: 'text',\n content: step.text,\n });\n await onProgress?.({\n type: 'text',\n subagentId: execution.id,\n subagentType: this.type,\n text: step.text,\n });\n }\n \n // Record tool calls\n if (step.toolCalls) {\n for (const toolCall of step.toolCalls) {\n await addStep({\n type: 'tool_call',\n content: `Calling ${toolCall.toolName}`,\n toolName: toolCall.toolName,\n toolInput: toolCall.input,\n });\n await onProgress?.({\n type: 'tool_call',\n subagentId: execution.id,\n subagentType: this.type,\n toolName: toolCall.toolName,\n toolInput: toolCall.input,\n });\n }\n }\n \n // Record tool results\n if (step.toolResults) {\n for (const toolResult of step.toolResults) {\n await addStep({\n type: 'tool_result',\n content: `Result from ${toolResult.toolName}`,\n toolName: toolResult.toolName,\n toolOutput: toolResult.output,\n });\n await onProgress?.({\n type: 'tool_result',\n subagentId: execution.id,\n subagentType: this.type,\n toolName: toolResult.toolName,\n toolOutput: toolResult.output,\n });\n }\n }\n },\n });\n \n // Parse the final result\n const parsedResult = this.parseResult(result.text, steps);\n \n // Mark as complete\n await subagentQueries.complete(execution.id, parsedResult);\n \n await onProgress?.({\n type: 'complete',\n subagentId: execution.id,\n subagentType: this.type,\n result: parsedResult,\n });\n \n return {\n success: true,\n result: parsedResult,\n steps,\n executionId: execution.id,\n };\n } catch (error: any) {\n const errorMessage = error.message || 'Unknown error';\n \n // Mark as error\n await subagentQueries.markError(execution.id, errorMessage);\n \n await onProgress?.({\n type: 'error',\n subagentId: execution.id,\n subagentType: this.type,\n error: errorMessage,\n });\n \n return {\n success: false,\n error: errorMessage,\n steps,\n executionId: execution.id,\n };\n }\n }\n \n /**\n * Run with streaming (for real-time progress in UI)\n */\n async *stream(options: SubagentRunOptions): AsyncGenerator<SubagentProgressEvent> {\n const events: SubagentProgressEvent[] = [];\n let resolveNext: ((event: SubagentProgressEvent | null) => void) | null = null;\n let done = false;\n \n // Queue for events\n const eventQueue: SubagentProgressEvent[] = [];\n \n // Start the run with progress callback\n const runPromise = this.run({\n ...options,\n onProgress: async (event) => {\n eventQueue.push(event);\n if (resolveNext) {\n resolveNext(eventQueue.shift()!);\n resolveNext = null;\n }\n },\n }).then((result) => {\n done = true;\n if (resolveNext) {\n resolveNext(null);\n }\n return result;\n });\n \n // Yield events as they come\n while (!done || eventQueue.length > 0) {\n if (eventQueue.length > 0) {\n yield eventQueue.shift()!;\n } else if (!done) {\n // Wait for next event\n const event = await new Promise<SubagentProgressEvent | null>((resolve) => {\n resolveNext = resolve;\n });\n if (event) {\n yield event;\n }\n }\n }\n \n // Wait for completion\n await runPromise;\n }\n}\n\n// Export types\nexport type { SubagentStep };\n","import { tool, type ToolSet } from 'ai';\nimport { z } from 'zod';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { readFile, stat, readdir } from 'node:fs/promises';\nimport { resolve, relative, isAbsolute, join, extname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { Subagent, SubagentRunOptions, SubagentStep } from '../subagent.js';\nimport { SUBAGENT_MODELS } from '../model.js';\nimport { truncateOutput } from '../../utils/truncate.js';\n\nconst execAsync = promisify(exec);\n\nconst MAX_OUTPUT_CHARS = 20_000;\nconst MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB for explore subagent\n\n/**\n * Search result from the Explore agent\n */\nexport interface SearchResult {\n query: string;\n summary: string;\n findings: SearchFinding[];\n filesSearched: number;\n matchCount: number;\n}\n\nexport interface SearchFinding {\n type: 'file' | 'match' | 'pattern';\n path: string;\n content?: string;\n lineNumber?: number;\n relevance: 'high' | 'medium' | 'low';\n context?: string;\n}\n\n/**\n * SearchSubagent - A mini-agent specialized for exploring codebases.\n * \n * Uses a smaller, faster model (Gemini 3 Flash Preview) and has access to:\n * - grep: Search for patterns in files\n * - glob: Find files by pattern\n * - read_file: Read file contents\n * - list_dir: List directory contents\n * \n * Returns structured search results to the main agent.\n */\nexport class SearchSubagent extends Subagent<SearchResult> {\n readonly type = 'search';\n readonly name = 'Explore Agent';\n \n constructor(model?: string) {\n super(model || SUBAGENT_MODELS.search);\n this.maxSteps = 15; // Limit steps for search tasks\n }\n \n protected getSystemPrompt(options: SubagentRunOptions): string {\n return `You are an Explore agent for codebase discovery. Your job is to map the structure, find relevant files, and surface connections based on the user's query.\n\nWorking Directory: ${options.workingDirectory}\n\nYou have these tools available:\n- grep: Search for patterns in files using ripgrep (rg)\n- glob: Find files matching a pattern\n- read_file: Read contents of a specific file\n- list_dir: List directory contents\n\n## Strategy - Explore in Parallel\n\nIMPORTANT: When exploring, run MULTIPLE searches in PARALLEL to cover different variations and related terms. Don't explore sequentially - batch your searches together!\n\nFor example, if asked \"how does authentication work\":\n- Search for \"auth\", \"login\", \"session\", \"jwt\", \"token\" all at once\n- Search in different likely directories: src/auth/, lib/auth/, services/auth/\n- Look for common patterns: AuthProvider, useAuth, authenticate, isAuthenticated\n\n**Parallel Search Patterns:**\n1. Try multiple naming conventions at once:\n - camelCase: \"getUserData\", \"handleAuth\"\n - snake_case: \"get_user_data\", \"handle_auth\"\n - PascalCase: \"UserService\", \"AuthProvider\"\n\n2. Search for related concepts together:\n - For \"database\": search \"db\", \"database\", \"query\", \"model\", \"schema\"\n - For \"api\": search \"endpoint\", \"route\", \"handler\", \"controller\", \"api\"\n\n3. Use glob AND grep together:\n - Find files: \\`*.auth.ts\\`, \\`*Auth*.tsx\\`, \\`auth/*.ts\\`\n - Search content: patterns, function names, class names\n\n## Execution Flow\n1. Start broad: list likely directories or entry points if the query is open-ended\n2. Run 2-4 parallel searches covering different angles of the query\n3. Review results and identify the most relevant files\n4. Read the key files to understand the full context\n5. Provide a clear summary with exact file paths and line numbers\n\nBe efficient - you have limited steps. Maximize coverage with parallel tool calls.\n\n## Output Format\nWhen done, provide a summary with:\n- Key files/locations found (with full paths)\n- Relevant code snippets showing the important parts\n- How the pieces connect together\n- Suggested next files to inspect (if more depth is needed)\n\nKeep your responses concise and focused on actionable information.`;\n }\n \n protected getTools(options: SubagentRunOptions): ToolSet {\n const workingDirectory = options.workingDirectory;\n \n return {\n grep: tool({\n description: 'Search for patterns in files using ripgrep. Returns matching lines with file paths and line numbers.',\n inputSchema: z.object({\n pattern: z.string().describe('The regex pattern to search for'),\n path: z.string().optional().describe('Subdirectory or file to search in (relative to working directory)'),\n fileType: z.string().optional().describe('File type to filter (e.g., \"ts\", \"js\", \"py\")'),\n maxResults: z.number().optional().default(50).describe('Maximum number of results to return'),\n }),\n execute: async ({ pattern, path, fileType, maxResults }) => {\n try {\n const searchPath = path \n ? resolve(workingDirectory, path) \n : workingDirectory;\n \n let args = ['rg', '--line-number', '--no-heading'];\n \n if (fileType) {\n args.push('--type', fileType);\n }\n \n args.push('--max-count', String(maxResults || 50));\n args.push('--', pattern, searchPath);\n \n const { stdout, stderr } = await execAsync(args.join(' '), {\n cwd: workingDirectory,\n maxBuffer: 5 * 1024 * 1024,\n timeout: 30000,\n });\n \n const output = truncateOutput(stdout || 'No matches found', MAX_OUTPUT_CHARS);\n const matchCount = (stdout || '').split('\\n').filter(Boolean).length;\n \n return {\n success: true,\n output,\n matchCount,\n pattern,\n };\n } catch (error: any) {\n // rg returns exit code 1 when no matches found\n if (error.code === 1 && !error.stderr) {\n return {\n success: true,\n output: 'No matches found',\n matchCount: 0,\n pattern,\n };\n }\n return {\n success: false,\n error: error.message,\n pattern,\n };\n }\n },\n }),\n \n glob: tool({\n description: 'Find files matching a glob pattern. Returns list of matching file paths.',\n inputSchema: z.object({\n pattern: z.string().describe('Glob pattern (e.g., \"**/*.ts\", \"src/**/*.tsx\", \"*.json\")'),\n maxResults: z.number().optional().default(100).describe('Maximum number of files to return'),\n }),\n execute: async ({ pattern, maxResults }) => {\n try {\n // Use find command with pattern matching\n const { stdout } = await execAsync(\n `find . -type f -name \"${pattern.replace('**/', '')}\" 2>/dev/null | head -n ${maxResults || 100}`,\n {\n cwd: workingDirectory,\n timeout: 30000,\n }\n );\n \n const files = stdout.trim().split('\\n').filter(Boolean);\n \n return {\n success: true,\n files,\n count: files.length,\n pattern,\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n pattern,\n };\n }\n },\n }),\n \n read_file: tool({\n description: 'Read the contents of a file. Use this to examine specific files found in search.',\n inputSchema: z.object({\n path: z.string().describe('Path to the file (relative to working directory or absolute)'),\n startLine: z.number().optional().describe('Start reading from this line (1-indexed)'),\n endLine: z.number().optional().describe('Stop reading at this line (1-indexed, inclusive)'),\n }),\n execute: async ({ path, startLine, endLine }) => {\n try {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(workingDirectory, path);\n \n if (!existsSync(absolutePath)) {\n return {\n success: false,\n error: `File not found: ${path}`,\n };\n }\n \n const stats = await stat(absolutePath);\n if (stats.size > MAX_FILE_SIZE) {\n return {\n success: false,\n error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`,\n };\n }\n \n let content = await readFile(absolutePath, 'utf-8');\n \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 content = lines.slice(start, end).join('\\n');\n }\n \n return {\n success: true,\n path: relative(workingDirectory, absolutePath),\n content: truncateOutput(content, MAX_OUTPUT_CHARS),\n lineCount: content.split('\\n').length,\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n }),\n \n list_dir: tool({\n description: 'List contents of a directory. Shows files and subdirectories.',\n inputSchema: z.object({\n path: z.string().optional().default('.').describe('Directory path (relative to working directory)'),\n recursive: z.boolean().optional().default(false).describe('List recursively (be careful with large directories)'),\n maxDepth: z.number().optional().default(2).describe('Maximum depth for recursive listing'),\n }),\n execute: async ({ path, recursive, maxDepth }) => {\n try {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(workingDirectory, path);\n \n if (!existsSync(absolutePath)) {\n return {\n success: false,\n error: `Directory not found: ${path}`,\n };\n }\n \n const stats = await stat(absolutePath);\n if (!stats.isDirectory()) {\n return {\n success: false,\n error: `Not a directory: ${path}`,\n };\n }\n \n if (recursive) {\n // Use find for recursive listing\n const { stdout } = await execAsync(\n `find . -maxdepth ${maxDepth} -type f 2>/dev/null | head -n 200`,\n {\n cwd: absolutePath,\n timeout: 10000,\n }\n );\n \n const files = stdout.trim().split('\\n').filter(Boolean);\n return {\n success: true,\n path: relative(workingDirectory, absolutePath) || '.',\n files,\n count: files.length,\n recursive: true,\n };\n } else {\n const entries = await readdir(absolutePath, { withFileTypes: true });\n const items = entries.slice(0, 200).map(e => ({\n name: e.name,\n type: e.isDirectory() ? 'directory' : 'file',\n }));\n \n return {\n success: true,\n path: relative(workingDirectory, absolutePath) || '.',\n items,\n count: items.length,\n };\n }\n } catch (error: any) {\n return {\n success: false,\n error: error.message,\n };\n }\n },\n }),\n };\n }\n \n protected parseResult(text: string, steps: SubagentStep[]): SearchResult {\n // Extract findings from tool results\n const findings: SearchFinding[] = [];\n let filesSearched = 0;\n let matchCount = 0;\n \n for (const step of steps) {\n if (step.type === 'tool_result' && step.toolOutput) {\n const output = step.toolOutput as any;\n \n if (step.toolName === 'grep' && output.success) {\n matchCount += output.matchCount || 0;\n \n // Parse grep output to extract findings\n const lines = (output.output || '').split('\\n').filter(Boolean).slice(0, 10);\n for (const line of lines) {\n // Format: path:line:content\n const match = line.match(/^([^:]+):(\\d+):(.*)$/);\n if (match) {\n findings.push({\n type: 'match',\n path: match[1],\n lineNumber: parseInt(match[2], 10),\n content: match[3].trim(),\n relevance: 'high',\n });\n }\n }\n } else if (step.toolName === 'glob' && output.success) {\n filesSearched += output.count || 0;\n \n for (const file of (output.files || []).slice(0, 5)) {\n findings.push({\n type: 'file',\n path: file,\n relevance: 'medium',\n });\n }\n } else if (step.toolName === 'read_file' && output.success) {\n findings.push({\n type: 'file',\n path: output.path,\n relevance: 'high',\n context: `${output.lineCount} lines`,\n });\n }\n }\n }\n \n // Get the query from the first user message (task)\n const query = steps.length > 0 \n ? (steps.find(s => s.type === 'text')?.content || '')\n : '';\n \n return {\n query,\n summary: text,\n findings: findings.slice(0, 20), // Limit findings\n filesSearched,\n matchCount,\n };\n }\n}\n\n// Factory function\nexport function createSearchSubagent(model?: string): SearchSubagent {\n return new SearchSubagent(model);\n}\n","/**\n * Semantic Search Tool\n * Uses Vector Gateway to perform semantic similarity search on indexed codebase\n */\n\nimport { tool } from 'ai';\nimport { z } from 'zod';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { minimatch } from 'minimatch';\nimport {\n getVectorClient,\n closeVectorClient,\n getRepoNamespace,\n getEmbeddingModel,\n} from '../semantic/index.js';\nimport { getConfig } from '../config/index.js';\nimport { SemanticMatch } from '../semantic/types.js';\n\nexport interface SemanticSearchToolOptions {\n workingDirectory: string;\n}\n\nexport interface SemanticSearchResult {\n success: boolean;\n query?: string;\n matches?: SemanticMatch[];\n totalMatches?: number;\n duration?: number;\n error?: string;\n}\n\nconst semanticSearchInputSchema = z.object({\n query: z\n .string()\n .describe('Natural language search query describing what you want to find'),\n topK: z\n .number()\n .optional()\n .default(10)\n .describe('Number of results to return (default: 10, max: 50)'),\n filePattern: z\n .string()\n .optional()\n .describe('Filter results by file glob pattern (e.g., \"*.ts\", \"src/**/*.py\")'),\n language: z\n .string()\n .optional()\n .describe('Filter by programming language (e.g., \"typescript\", \"python\")'),\n});\n\n/**\n * Create the semantic_search tool\n */\nexport function createSemanticSearchTool(options: SemanticSearchToolOptions) {\n return tool({\n description: `Search the codebase using semantic similarity. This tool finds code by understanding its meaning, not just matching text.\n\nUse this tool when:\n- You need to understand how something works in the codebase\n- You're looking for code related to a concept (e.g., \"authentication\", \"database queries\")\n- You want to find implementations of features\n- The user asks \"where is X?\" or \"how does Y work?\"\n\nThis tool requires the repository to be indexed first with 'sparkecoder index'.\n\nReturns matching code snippets with file paths, line numbers, and relevance scores.`,\n\n inputSchema: semanticSearchInputSchema,\n\n execute: async ({\n query,\n topK,\n filePattern,\n language,\n }: z.infer<typeof semanticSearchInputSchema>): Promise<SemanticSearchResult> => {\n const startTime = Date.now();\n\n try {\n const config = getConfig();\n\n const namespace = await getRepoNamespace(\n options.workingDirectory,\n config.resolvedVectorGateway.namespace\n );\n\n if (!namespace) {\n return {\n success: false,\n error: 'Repository namespace not found. Ensure this is a git repository with a remote configured.',\n };\n }\n\n const client = getVectorClient();\n if (!client) {\n return {\n success: false,\n error: 'Remote server not configured. Set SPARKECODER_REMOTE_URL/SPARKECODER_AUTH_KEY or run sparkecoder to register.',\n };\n }\n\n try {\n const limitedTopK = Math.min(Math.max(1, topK), 50);\n\n const embeddingModel = getEmbeddingModel();\n const result = await client.search.queryAndWait(query, {\n namespace,\n topK: limitedTopK * 2,\n includeMetadata: true,\n embeddingModel,\n });\n\n const matches: SemanticMatch[] = [];\n\n for (const match of result.matches) {\n const metadata = match.metadata as Record<string, unknown> | undefined;\n if (!metadata) continue;\n\n const filePath = metadata.filePath as string;\n const startLine = metadata.startLine as number;\n const endLine = metadata.endLine as number;\n const matchLanguage = metadata.language as string;\n const symbolName = metadata.symbolName as string | undefined;\n\n if (filePattern) {\n const matchesPattern = minimatch(filePath, filePattern, { dot: true });\n if (!matchesPattern) continue;\n }\n\n if (language && matchLanguage !== language.toLowerCase()) {\n continue;\n }\n\n const fullPath = join(options.workingDirectory, filePath);\n if (!existsSync(fullPath)) {\n continue;\n }\n\n let snippet = '';\n try {\n const content = readFileSync(fullPath, 'utf-8');\n const lines = content.split('\\n');\n const snippetLines = lines.slice(\n Math.max(0, startLine - 1),\n Math.min(lines.length, endLine)\n );\n snippet = snippetLines.join('\\n');\n\n if (snippet.length > 500) {\n snippet = snippet.slice(0, 500) + '\\n... (truncated)';\n }\n } catch {\n // Ignore read errors\n }\n\n matches.push({\n filePath,\n startLine,\n endLine,\n score: match.score,\n snippet,\n symbolName,\n language: matchLanguage,\n });\n\n if (matches.length >= limitedTopK) {\n break;\n }\n }\n\n return {\n success: true,\n query,\n matches,\n totalMatches: matches.length,\n duration: Date.now() - startTime,\n };\n } finally {\n await closeVectorClient();\n }\n } catch (error) {\n return {\n success: false,\n error: `Semantic search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n },\n });\n}\n","/**\n * Git remote to namespace resolution\n * Converts git remote URLs to TurboPuffer namespaces\n */\n\nimport { execSync } from 'node:child_process';\n\n/**\n * Get the git remote URL for a repository\n * Returns null if not a git repo or no remote configured\n */\nexport function getGitRemoteUrl(workingDirectory: string): string | null {\n try {\n const result = execSync('git remote get-url origin', {\n cwd: workingDirectory,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Parse a git remote URL to extract org and repo name\n * Supports:\n * - https://github.com/org/repo.git\n * - git@github.com:org/repo.git\n * - https://gitlab.com/org/repo\n * - ssh://git@bitbucket.org/org/repo.git\n */\nexport function parseGitRemoteUrl(url: string): { org: string; repo: string } | null {\n // Remove .git suffix if present\n const cleanUrl = url.replace(/\\.git$/, '');\n\n // Try SSH format: git@github.com:org/repo\n const sshMatch = cleanUrl.match(/git@[^:]+:([^/]+)\\/(.+)$/);\n if (sshMatch) {\n return { org: sshMatch[1], repo: sshMatch[2] };\n }\n\n // Try HTTPS format: https://github.com/org/repo\n const httpsMatch = cleanUrl.match(/https?:\\/\\/[^/]+\\/([^/]+)\\/(.+)$/);\n if (httpsMatch) {\n return { org: httpsMatch[1], repo: httpsMatch[2] };\n }\n\n // Try SSH with protocol: ssh://git@github.com/org/repo\n const sshProtoMatch = cleanUrl.match(/ssh:\\/\\/[^/]+\\/([^/]+)\\/(.+)$/);\n if (sshProtoMatch) {\n return { org: sshProtoMatch[1], repo: sshProtoMatch[2] };\n }\n\n return null;\n}\n\n/**\n * Sanitize a string for use in a namespace\n * - Lowercase\n * - Replace non-alphanumeric with underscores\n * - Remove leading/trailing underscores\n * - Collapse multiple underscores\n */\nfunction sanitizeForNamespace(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '_')\n .replace(/^_+|_+$/g, '')\n .replace(/_+/g, '_');\n}\n\n/**\n * Get the namespace for a repository\n * Format: sparkecoder_{org}_{repo}\n * \n * @param workingDirectory - The repo working directory\n * @param configuredNamespace - Optional namespace override from config\n * @returns The namespace string, or null if not a git repo\n */\nexport async function getRepoNamespace(\n workingDirectory: string,\n configuredNamespace?: string | null\n): Promise<string | null> {\n // Use configured namespace if provided\n if (configuredNamespace) {\n return configuredNamespace;\n }\n\n // Get git remote URL\n const remoteUrl = getGitRemoteUrl(workingDirectory);\n if (!remoteUrl) {\n return null;\n }\n\n // Parse org and repo\n const parsed = parseGitRemoteUrl(remoteUrl);\n if (!parsed) {\n return null;\n }\n\n // Build namespace\n const org = sanitizeForNamespace(parsed.org);\n const repo = sanitizeForNamespace(parsed.repo);\n return `sparkecoder_${org}_${repo}`;\n}\n\n/**\n * Check if the working directory is a git repository\n */\nexport function isGitRepository(workingDirectory: string): boolean {\n try {\n execSync('git rev-parse --git-dir', {\n cwd: workingDirectory,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the current git branch name\n */\nexport function getCurrentBranch(workingDirectory: string): string | null {\n try {\n const result = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: workingDirectory,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Get the current git commit hash (short form)\n */\nexport function getCurrentCommit(workingDirectory: string): string | null {\n try {\n const result = execSync('git rev-parse --short HEAD', {\n cwd: workingDirectory,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.trim();\n } catch {\n return null;\n }\n}\n","/**\n * Content hashing utilities for deduplication\n * Uses SHA-256 for deterministic content hashing\n */\n\nimport { createHash } from 'node:crypto';\n\n/**\n * Compute SHA-256 hash of content\n * Returns first 16 characters of hex digest for a reasonable ID length\n */\nexport function computeContentHash(content: string): string {\n const hash = createHash('sha256');\n hash.update(content, 'utf-8');\n return hash.digest('hex').slice(0, 16);\n}\n\n/**\n * Compute a full SHA-256 hash (64 chars)\n */\nexport function computeFullHash(content: string): string {\n const hash = createHash('sha256');\n hash.update(content, 'utf-8');\n return hash.digest('hex');\n}\n\n/**\n * Generate a chunk ID from content hash and chunk index\n * Format: {contentHash}_{chunkIndex}\n */\nexport function generateChunkId(contentHash: string, chunkIndex: number): string {\n return `${contentHash}_${chunkIndex}`;\n}\n\n/**\n * Parse a chunk ID to extract content hash and chunk index\n */\nexport function parseChunkId(chunkId: string): { contentHash: string; chunkIndex: number } | null {\n const match = chunkId.match(/^([a-f0-9]+)_(\\d+)$/);\n if (!match) {\n return null;\n }\n return {\n contentHash: match[1],\n chunkIndex: parseInt(match[2], 10),\n };\n}\n\n/**\n * Compute hash for a file's content\n * Normalizes line endings for consistent hashing across platforms\n */\nexport function computeFileHash(content: string): string {\n // Normalize line endings to LF\n const normalized = content.replace(/\\r\\n/g, '\\n');\n return computeContentHash(normalized);\n}\n","/**\n * Hybrid code chunking\n * - Semantic chunking for code files (by function/class)\n * - Sliding window for documentation and text files\n */\n\nimport { extname, basename } from 'node:path';\nimport { Chunk, ChunkMetadata, ChunkType } from './types.js';\nimport { computeContentHash, generateChunkId } from './hasher.js';\n\n// Language detection by file extension\nconst LANGUAGE_MAP: Record<string, string> = {\n '.ts': 'typescript',\n '.tsx': 'typescript',\n '.js': 'javascript',\n '.jsx': 'javascript',\n '.mjs': 'javascript',\n '.cjs': 'javascript',\n '.py': 'python',\n '.go': 'go',\n '.rs': 'rust',\n '.java': 'java',\n '.kt': 'kotlin',\n '.swift': 'swift',\n '.rb': 'ruby',\n '.php': 'php',\n '.c': 'c',\n '.cpp': 'cpp',\n '.h': 'c',\n '.hpp': 'cpp',\n '.cs': 'csharp',\n '.md': 'markdown',\n '.mdx': 'markdown',\n '.txt': 'text',\n '.json': 'json',\n '.yaml': 'yaml',\n '.yml': 'yaml',\n '.toml': 'toml',\n '.xml': 'xml',\n '.html': 'html',\n '.css': 'css',\n '.scss': 'scss',\n '.less': 'less',\n '.sql': 'sql',\n '.sh': 'shell',\n '.bash': 'shell',\n '.zsh': 'shell',\n};\n\n// Languages that support semantic chunking\nconst SEMANTIC_LANGUAGES = new Set([\n 'typescript',\n 'javascript',\n 'python',\n 'go',\n 'rust',\n 'java',\n 'kotlin',\n 'swift',\n 'ruby',\n 'php',\n 'c',\n 'cpp',\n 'csharp',\n]);\n\n// Sliding window config\nconst SLIDING_WINDOW_SIZE = 1500; // ~500 tokens\nconst SLIDING_WINDOW_OVERLAP = 300; // ~100 tokens\n\n// Max chunk size (to avoid very long embeddings)\nconst MAX_CHUNK_SIZE = 4000; // ~1300 tokens\n\n/**\n * Detect language from file path\n */\nexport function detectLanguage(filePath: string): string {\n const ext = extname(filePath).toLowerCase();\n return LANGUAGE_MAP[ext] || 'unknown';\n}\n\n/**\n * Check if a language supports semantic chunking\n */\nexport function supportsSemanticChunking(language: string): boolean {\n return SEMANTIC_LANGUAGES.has(language);\n}\n\n/**\n * Chunk a file into embedding-ready chunks\n */\nexport function chunkFile(filePath: string, content: string): Chunk[] {\n const language = detectLanguage(filePath);\n \n // Skip empty files\n if (!content.trim()) {\n return [];\n }\n\n // Use semantic chunking for supported languages, sliding window otherwise\n if (supportsSemanticChunking(language)) {\n return chunkCodeSemantic(filePath, content, language);\n } else {\n return chunkSlidingWindow(filePath, content, language);\n }\n}\n\n/**\n * Semantic chunking for code files\n * Extracts functions, classes, and significant blocks\n */\nfunction chunkCodeSemantic(filePath: string, content: string, language: string): Chunk[] {\n const chunks: Chunk[] = [];\n const lines = content.split('\\n');\n \n // Simple regex-based extraction (tree-sitter would be more accurate)\n // This is a pragmatic approach that works for most cases\n \n const blocks = extractCodeBlocks(lines, language);\n \n if (blocks.length === 0) {\n // Fall back to sliding window if no blocks found\n return chunkSlidingWindow(filePath, content, language);\n }\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i];\n const blockContent = lines.slice(block.startLine, block.endLine + 1).join('\\n');\n \n // Skip very small blocks\n if (blockContent.trim().length < 50) {\n continue;\n }\n\n // If block is too large, split it\n if (blockContent.length > MAX_CHUNK_SIZE) {\n const subChunks = splitLargeBlock(filePath, blockContent, block.startLine, language, block.type, block.name);\n chunks.push(...subChunks);\n } else {\n const contentHash = computeContentHash(blockContent);\n const chunkId = generateChunkId(contentHash, i);\n \n chunks.push({\n id: chunkId,\n text: buildChunkText(filePath, blockContent, block.name),\n contentHash,\n chunkIndex: i,\n metadata: {\n filePath,\n startLine: block.startLine + 1, // 1-indexed\n endLine: block.endLine + 1,\n language,\n chunkType: block.type as ChunkType,\n symbolName: block.name,\n },\n });\n }\n }\n\n // If no meaningful chunks, fall back to sliding window\n if (chunks.length === 0) {\n return chunkSlidingWindow(filePath, content, language);\n }\n\n return reindexChunks(chunks);\n}\n\n/**\n * Extract code blocks (functions, classes) from source code\n */\nfunction extractCodeBlocks(\n lines: string[],\n language: string\n): Array<{ startLine: number; endLine: number; type: string; name?: string }> {\n const blocks: Array<{ startLine: number; endLine: number; type: string; name?: string }> = [];\n \n // Language-specific patterns\n const patterns = getLanguagePatterns(language);\n \n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n \n // Check for function/class definitions\n for (const pattern of patterns) {\n const match = line.match(pattern.regex);\n if (match) {\n const name = match[1];\n const endLine = findBlockEnd(lines, i, language);\n \n blocks.push({\n startLine: i,\n endLine,\n type: pattern.type,\n name,\n });\n \n i = endLine + 1;\n break;\n }\n }\n \n i++;\n }\n\n // Merge adjacent small blocks\n return mergeSmallBlocks(blocks, lines);\n}\n\n/**\n * Get regex patterns for extracting code blocks\n */\nfunction getLanguagePatterns(language: string): Array<{ regex: RegExp; type: string }> {\n switch (language) {\n case 'typescript':\n case 'javascript':\n return [\n { regex: /^\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)/, type: 'function' },\n { regex: /^\\s*(?:export\\s+)?(?:const|let|var)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?(?:\\([^)]*\\)|[^=])\\s*=>/, type: 'function' },\n { regex: /^\\s*(?:export\\s+)?class\\s+(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:export\\s+)?interface\\s+(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:export\\s+)?type\\s+(\\w+)/, type: 'class' },\n ];\n case 'python':\n return [\n { regex: /^\\s*(?:async\\s+)?def\\s+(\\w+)/, type: 'function' },\n { regex: /^\\s*class\\s+(\\w+)/, type: 'class' },\n ];\n case 'go':\n return [\n { regex: /^\\s*func\\s+(?:\\([^)]+\\)\\s+)?(\\w+)/, type: 'function' },\n { regex: /^\\s*type\\s+(\\w+)\\s+struct/, type: 'class' },\n { regex: /^\\s*type\\s+(\\w+)\\s+interface/, type: 'class' },\n ];\n case 'rust':\n return [\n { regex: /^\\s*(?:pub\\s+)?(?:async\\s+)?fn\\s+(\\w+)/, type: 'function' },\n { regex: /^\\s*(?:pub\\s+)?struct\\s+(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:pub\\s+)?impl\\s+(?:<[^>]+>\\s+)?(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:pub\\s+)?trait\\s+(\\w+)/, type: 'class' },\n ];\n case 'java':\n case 'kotlin':\n return [\n { regex: /^\\s*(?:public|private|protected)?\\s*(?:static\\s+)?(?:\\w+\\s+)?(\\w+)\\s*\\(/, type: 'function' },\n { regex: /^\\s*(?:public|private|protected)?\\s*(?:abstract\\s+)?class\\s+(\\w+)/, type: 'class' },\n { regex: /^\\s*(?:public|private|protected)?\\s*interface\\s+(\\w+)/, type: 'class' },\n ];\n default:\n return [\n { regex: /^\\s*(?:function|def|fn|func)\\s+(\\w+)/, type: 'function' },\n { regex: /^\\s*class\\s+(\\w+)/, type: 'class' },\n ];\n }\n}\n\n/**\n * Find the end of a code block (matching braces/indentation)\n */\nfunction findBlockEnd(lines: string[], startLine: number, language: string): number {\n // Python uses indentation\n if (language === 'python') {\n return findPythonBlockEnd(lines, startLine);\n }\n \n // Most languages use braces\n return findBraceBlockEnd(lines, startLine);\n}\n\n/**\n * Find block end for brace-based languages\n */\nfunction findBraceBlockEnd(lines: string[], startLine: number): number {\n let braceCount = 0;\n let foundOpen = false;\n \n for (let i = startLine; i < lines.length; i++) {\n const line = lines[i];\n \n for (const char of line) {\n if (char === '{') {\n braceCount++;\n foundOpen = true;\n } else if (char === '}') {\n braceCount--;\n }\n }\n \n if (foundOpen && braceCount === 0) {\n return i;\n }\n }\n \n // If no matching brace found, return a reasonable chunk\n return Math.min(startLine + 50, lines.length - 1);\n}\n\n/**\n * Find block end for Python (indentation-based)\n */\nfunction findPythonBlockEnd(lines: string[], startLine: number): number {\n const startIndent = getIndentLevel(lines[startLine]);\n \n for (let i = startLine + 1; i < lines.length; i++) {\n const line = lines[i];\n \n // Skip empty lines\n if (!line.trim()) {\n continue;\n }\n \n const indent = getIndentLevel(line);\n \n // Block ends when we return to same or lower indentation\n if (indent <= startIndent && line.trim()) {\n return i - 1;\n }\n }\n \n return lines.length - 1;\n}\n\n/**\n * Get indentation level of a line\n */\nfunction getIndentLevel(line: string): number {\n const match = line.match(/^(\\s*)/);\n return match ? match[1].length : 0;\n}\n\n/**\n * Merge small adjacent blocks\n */\nfunction mergeSmallBlocks(\n blocks: Array<{ startLine: number; endLine: number; type: string; name?: string }>,\n lines: string[]\n): Array<{ startLine: number; endLine: number; type: string; name?: string }> {\n if (blocks.length === 0) {\n return blocks;\n }\n\n const merged: typeof blocks = [];\n let current = blocks[0];\n\n for (let i = 1; i < blocks.length; i++) {\n const next = blocks[i];\n const currentContent = lines.slice(current.startLine, current.endLine + 1).join('\\n');\n const gap = next.startLine - current.endLine;\n \n // Merge if current block is small and gap is small\n if (currentContent.length < 500 && gap <= 3) {\n current = {\n startLine: current.startLine,\n endLine: next.endLine,\n type: 'block',\n name: current.name,\n };\n } else {\n merged.push(current);\n current = next;\n }\n }\n \n merged.push(current);\n return merged;\n}\n\n/**\n * Split a large block into smaller chunks\n */\nfunction splitLargeBlock(\n filePath: string,\n content: string,\n startLine: number,\n language: string,\n type: string,\n name?: string\n): Chunk[] {\n const chunks: Chunk[] = [];\n const lines = content.split('\\n');\n \n let currentStart = 0;\n let currentChunk = '';\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const newChunk = currentChunk + (currentChunk ? '\\n' : '') + line;\n \n if (newChunk.length > MAX_CHUNK_SIZE && currentChunk) {\n const contentHash = computeContentHash(currentChunk);\n chunks.push({\n id: generateChunkId(contentHash, chunks.length),\n text: buildChunkText(filePath, currentChunk, name),\n contentHash,\n chunkIndex: chunks.length,\n metadata: {\n filePath,\n startLine: startLine + currentStart + 1,\n endLine: startLine + i,\n language,\n chunkType: type as ChunkType,\n symbolName: name,\n },\n });\n \n currentStart = i;\n currentChunk = line;\n } else {\n currentChunk = newChunk;\n }\n }\n \n // Add remaining content\n if (currentChunk.trim()) {\n const contentHash = computeContentHash(currentChunk);\n chunks.push({\n id: generateChunkId(contentHash, chunks.length),\n text: buildChunkText(filePath, currentChunk, name),\n contentHash,\n chunkIndex: chunks.length,\n metadata: {\n filePath,\n startLine: startLine + currentStart + 1,\n endLine: startLine + lines.length,\n language,\n chunkType: type as ChunkType,\n symbolName: name,\n },\n });\n }\n\n return chunks;\n}\n\n/**\n * Sliding window chunking for non-code files\n */\nfunction chunkSlidingWindow(filePath: string, content: string, language: string): Chunk[] {\n const chunks: Chunk[] = [];\n \n // If content is small enough, return as single chunk\n if (content.length <= MAX_CHUNK_SIZE) {\n const contentHash = computeContentHash(content);\n chunks.push({\n id: generateChunkId(contentHash, 0),\n text: buildChunkText(filePath, content),\n contentHash,\n chunkIndex: 0,\n metadata: {\n filePath,\n startLine: 1,\n endLine: content.split('\\n').length,\n language,\n chunkType: 'sliding',\n },\n });\n return chunks;\n }\n\n // Split by lines to preserve line boundaries\n const lines = content.split('\\n');\n let currentStart = 0;\n let currentChunk = '';\n let currentLineStart = 0;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const newChunk = currentChunk + (currentChunk ? '\\n' : '') + line;\n \n if (newChunk.length >= SLIDING_WINDOW_SIZE) {\n const contentHash = computeContentHash(currentChunk || newChunk);\n chunks.push({\n id: generateChunkId(contentHash, chunks.length),\n text: buildChunkText(filePath, currentChunk || newChunk),\n contentHash,\n chunkIndex: chunks.length,\n metadata: {\n filePath,\n startLine: currentLineStart + 1,\n endLine: i + 1,\n language,\n chunkType: 'sliding',\n },\n });\n \n // Move back for overlap\n const overlapLines = Math.floor(SLIDING_WINDOW_OVERLAP / 50); // rough estimate\n currentLineStart = Math.max(currentStart, i - overlapLines);\n currentChunk = lines.slice(currentLineStart, i + 1).join('\\n');\n currentStart = currentLineStart;\n } else {\n currentChunk = newChunk;\n }\n }\n\n // Add remaining content\n if (currentChunk.trim() && currentChunk.length > 50) {\n const contentHash = computeContentHash(currentChunk);\n chunks.push({\n id: generateChunkId(contentHash, chunks.length),\n text: buildChunkText(filePath, currentChunk),\n contentHash,\n chunkIndex: chunks.length,\n metadata: {\n filePath,\n startLine: currentLineStart + 1,\n endLine: lines.length,\n language,\n chunkType: 'sliding',\n },\n });\n }\n\n return reindexChunks(chunks);\n}\n\n/**\n * Build the text to embed, including file context\n */\nfunction buildChunkText(filePath: string, content: string, symbolName?: string): string {\n const fileName = basename(filePath);\n let text = `File: ${filePath}\\n`;\n \n if (symbolName) {\n text += `Symbol: ${symbolName}\\n`;\n }\n \n text += `\\n${content}`;\n return text;\n}\n\n/**\n * Re-index chunks to ensure sequential chunk indices\n */\nfunction reindexChunks(chunks: Chunk[]): Chunk[] {\n return chunks.map((chunk, index) => ({\n ...chunk,\n chunkIndex: index,\n id: generateChunkId(chunk.contentHash, index),\n }));\n}\n","/**\n * Vector client - uses remote server API for vector operations\n * This removes the need for the private vector SDK in the client\n */\n\nimport { getConfig } from '../config/index.js';\n\n// Types for vector operations (matching remote server API)\nexport interface EmbeddingRequest {\n texts: Array<{ id: string; text: string; document: Record<string, unknown> }>;\n namespace: string;\n embeddingModel?: string;\n}\n\nexport interface EmbeddingError {\n id?: string;\n error: string;\n}\n\nexport interface EmbeddingResult {\n processedCount: number;\n failedCount: number;\n errors?: EmbeddingError[];\n}\n\nexport interface SearchRequest {\n query: string;\n namespace: string;\n topK?: number;\n embeddingModel?: string;\n}\n\nexport interface SearchMatch {\n id: string;\n score: number;\n metadata?: Record<string, unknown>;\n}\n\nexport interface SearchResult {\n matches: SearchMatch[];\n}\n\n// Remote vector client state\nlet remoteServerUrl: string | null = null;\nlet authKey: string | null = null;\n\n/**\n * Initialize the vector client with remote server config\n */\nexport function initVectorClient(serverUrl: string, key: string) {\n remoteServerUrl = serverUrl.replace(/\\/$/, '');\n authKey = key;\n}\n\n/**\n * Check if vector client is configured\n */\nexport function isVectorClientConfigured(): boolean {\n return !!remoteServerUrl && !!authKey;\n}\n\n/**\n * HTTP helper for remote vector API calls\n */\nasync function vectorApi<T>(\n path: string,\n options: { method?: string; body?: unknown } = {}\n): Promise<T> {\n if (!remoteServerUrl || !authKey) {\n throw new Error('Vector client not initialized - remote server not configured');\n }\n \n const url = `${remoteServerUrl}/vectors${path}`;\n const init: RequestInit = {\n method: options.method || 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${authKey}`,\n },\n };\n \n if (options.body) {\n init.body = JSON.stringify(options.body);\n }\n \n const response = await fetch(url, init);\n \n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n throw new Error(error.error || `HTTP ${response.status}`);\n }\n \n return response.json() as Promise<T>;\n}\n\n/**\n * Remote vector client that calls the remote server\n * Implements same interface as the old VectorClient SDK\n */\nexport const remoteVectorClient = {\n embeddings: {\n /**\n * Create embeddings and store in vector DB\n */\n async createAndWait(\n texts: Array<{ id: string; text: string; document: Record<string, unknown> }>,\n options: {\n namespace: string;\n embeddingModel?: string;\n }\n ): Promise<EmbeddingResult> {\n return vectorApi<EmbeddingResult>('/embed', {\n method: 'POST',\n body: {\n texts,\n namespace: options.namespace,\n embeddingModel: options.embeddingModel,\n },\n });\n },\n },\n \n search: {\n /**\n * Query vectors using semantic search\n */\n async queryAndWait(\n query: string,\n options: {\n namespace: string;\n topK?: number;\n includeMetadata?: boolean;\n embeddingModel?: string;\n }\n ): Promise<SearchResult> {\n return vectorApi<SearchResult>('/search', {\n method: 'POST',\n body: {\n query,\n namespace: options.namespace,\n topK: options.topK || 10,\n embeddingModel: options.embeddingModel,\n },\n });\n },\n },\n \n /**\n * Delete a namespace (if supported)\n */\n async deleteNamespace(namespace: string): Promise<void> {\n await vectorApi(`/namespace/${encodeURIComponent(namespace)}`, {\n method: 'DELETE',\n });\n },\n \n /**\n * Close client (no-op for HTTP client)\n */\n async close(): Promise<void> {\n // No-op - HTTP connections don't need cleanup\n },\n};\n\n// Type alias for the vector client\nexport type VectorClient = typeof remoteVectorClient;\n\n/**\n * Get the Vector client\n * Returns null if remote server is not configured\n */\nexport function getVectorClient(): VectorClient | null {\n if (!isVectorClientConfigured()) {\n // Try to initialize from config\n try {\n const config = getConfig();\n if (config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey) {\n initVectorClient(config.resolvedRemoteServer.url, config.resolvedRemoteServer.authKey);\n } else {\n return null;\n }\n } catch {\n return null;\n }\n }\n \n return remoteVectorClient;\n}\n\n/**\n * Close the vector client (no-op for HTTP client)\n */\nexport async function closeVectorClient(): Promise<void> {\n // No-op - HTTP connections don't need cleanup\n}\n\n/**\n * Check if Vector Gateway is configured (via remote server)\n */\nexport function isVectorGatewayConfigured(): boolean {\n try {\n const config = getConfig();\n return !!(config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey);\n } catch {\n return false;\n }\n}\n\n/**\n * Get the configured embedding model\n */\nexport function getEmbeddingModel(): string {\n try {\n const config = getConfig();\n return config.resolvedVectorGateway.embeddingModel;\n } catch {\n return 'gemini-embedding-001';\n }\n}\n","/**\n * Repository indexing pipeline\n * Walks the repo, chunks files, and sends to Vector Gateway for embedding\n */\n\nimport { readFileSync, statSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport { minimatch } from 'minimatch';\nimport { getConfig } from '../config/index.js';\nimport { getDb, indexedChunkQueries, indexStatusQueries } from '../db/index.js';\nimport { Chunk, IndexOptions, IndexProgress, IndexResult, IndexStatus } from './types.js';\nimport { getRepoNamespace, isGitRepository } from './namespace.js';\nimport { chunkFile } from './chunker.js';\nimport { getVectorClient, closeVectorClient, getEmbeddingModel } from './client.js';\n\n// Max file size to index (1MB)\nconst MAX_FILE_SIZE = 1024 * 1024;\n\n// Batch size and concurrency for embedding requests\nconst EMBEDDING_BATCH_SIZE = 50;\nconst EMBEDDING_CONCURRENCY = 5;\nconst EMBEDDING_RETRIES = 2;\n\nfunction parsePositiveInt(value: string | undefined, fallback: number): number {\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return fallback;\n }\n return Math.floor(parsed);\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) {\n return error.message || 'Unknown error';\n }\n if (typeof error === 'string') {\n return error;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return 'Unknown error';\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Check if a path matches any exclude pattern\n */\nfunction isPathExcluded(relativePath: string, exclude: string[]): boolean {\n return exclude.some((pattern) => {\n // Direct match\n if (minimatch(relativePath, pattern, { dot: true })) {\n return true;\n }\n // For directory patterns like \"node_modules/**\", also check if the path\n // starts with the directory name (for skipping entire directories)\n if (pattern.endsWith('/**')) {\n const dirPattern = pattern.slice(0, -3); // Remove \"/**\"\n if (relativePath === dirPattern || relativePath.startsWith(dirPattern + '/')) {\n return true;\n }\n }\n return false;\n });\n}\n\n/**\n * Walk directory and collect files matching patterns\n */\nasync function walkDirectory(\n dir: string,\n include: string[],\n exclude: string[],\n baseDir: string\n): Promise<string[]> {\n const { readdirSync } = await import('node:fs');\n const { join, relative } = await import('node:path');\n \n const files: string[] = [];\n \n function walk(currentDir: string) {\n let entries;\n try {\n entries = readdirSync(currentDir, { withFileTypes: true });\n } catch {\n // Skip directories we can't read\n return;\n }\n \n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n const relativePath = relative(baseDir, fullPath);\n \n // Check exclusions first\n if (isPathExcluded(relativePath, exclude)) {\n continue;\n }\n \n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile()) {\n // Check inclusions\n const isIncluded = include.some((pattern) => {\n return minimatch(relativePath, pattern, { dot: true });\n });\n \n if (isIncluded) {\n files.push(fullPath);\n }\n }\n }\n }\n \n walk(dir);\n return files;\n}\n\n/**\n * Check if a file should be skipped (binary, too large, etc.)\n */\nfunction shouldSkipFile(filePath: string): { skip: boolean; reason?: string } {\n try {\n const stats = statSync(filePath);\n \n if (stats.size > MAX_FILE_SIZE) {\n return { skip: true, reason: 'File too large (>1MB)' };\n }\n \n if (stats.size === 0) {\n return { skip: true, reason: 'Empty file' };\n }\n \n // For text detection, just try to read the file as UTF-8\n // If it fails or has null bytes, it's likely binary\n try {\n const content = readFileSync(filePath, 'utf-8');\n // Check for null bytes in first 1000 chars (binary indicator)\n const sample = content.slice(0, 1000);\n if (sample.includes('\\0')) {\n return { skip: true, reason: 'Binary file' };\n }\n } catch {\n return { skip: true, reason: 'Cannot read as text' };\n }\n \n return { skip: false };\n } catch (error) {\n return { skip: true, reason: `Error reading file: ${error}` };\n }\n}\n\n/**\n * Index a repository for semantic search\n */\nexport async function indexRepository(options: IndexOptions): Promise<IndexResult> {\n const startTime = Date.now();\n const errors: Array<{ file: string; error: string }> = [];\n \n const progress: IndexProgress = {\n phase: 'scanning',\n totalFiles: 0,\n processedFiles: 0,\n totalChunks: 0,\n newChunks: 0,\n skippedChunks: 0,\n };\n \n const reportProgress = () => {\n if (options.onProgress) {\n options.onProgress({ ...progress });\n }\n };\n\n // Check if git repository\n if (!isGitRepository(options.workingDirectory)) {\n return {\n success: false,\n namespace: '',\n totalFiles: 0,\n totalChunks: 0,\n newChunks: 0,\n skippedChunks: 0,\n failedChunks: 0,\n duration: Date.now() - startTime,\n errors: [{ file: '', error: 'Not a git repository' }],\n };\n }\n\n // Get config\n const config = getConfig();\n const { include, exclude, namespace: configNamespace } = config.resolvedVectorGateway;\n\n // Get namespace\n const namespace = await getRepoNamespace(options.workingDirectory, configNamespace);\n if (!namespace) {\n return {\n success: false,\n namespace: '',\n totalFiles: 0,\n totalChunks: 0,\n newChunks: 0,\n skippedChunks: 0,\n failedChunks: 0,\n duration: Date.now() - startTime,\n errors: [{ file: '', error: 'Could not determine repository namespace. Ensure git remote is configured.' }],\n };\n }\n\n // Get vector client\n const client = getVectorClient();\n if (!client) {\n return {\n success: false,\n namespace,\n totalFiles: 0,\n totalChunks: 0,\n newChunks: 0,\n skippedChunks: 0,\n failedChunks: 0,\n duration: Date.now() - startTime,\n errors: [{ file: '', error: 'Remote server not configured. Set SPARKECODER_REMOTE_URL/SPARKECODER_AUTH_KEY or remoteServer in sparkecoder.config.json' }],\n };\n }\n\n try {\n // Phase 1: Scan files\n progress.phase = 'scanning';\n reportProgress();\n \n const files = await walkDirectory(\n options.workingDirectory,\n include,\n exclude,\n options.workingDirectory\n );\n \n progress.totalFiles = files.length;\n reportProgress();\n\n // Phase 2: Chunk files\n progress.phase = 'chunking';\n reportProgress();\n \n const allChunks: Chunk[] = [];\n \n for (const filePath of files) {\n const relativePath = relative(options.workingDirectory, filePath);\n progress.currentFile = relativePath;\n \n const skipCheck = shouldSkipFile(filePath);\n if (skipCheck.skip) {\n if (options.verbose) {\n console.log(`Skipping ${relativePath}: ${skipCheck.reason}`);\n }\n progress.processedFiles++;\n reportProgress();\n continue;\n }\n \n try {\n const content = readFileSync(filePath, 'utf-8');\n const chunks = chunkFile(relativePath, content);\n allChunks.push(...chunks);\n progress.totalChunks += chunks.length;\n } catch (error) {\n errors.push({ file: relativePath, error: String(error) });\n }\n \n progress.processedFiles++;\n reportProgress();\n }\n\n // Phase 3: Check existing hashes\n progress.phase = 'checking';\n reportProgress();\n \n const db = getDb();\n const existingHashes = new Set<string>();\n \n if (!options.force) {\n // Get all existing chunk IDs for this namespace\n const existingChunks = await indexedChunkQueries.getByNamespace(db, namespace);\n for (const chunk of existingChunks) {\n existingHashes.add(chunk.id);\n }\n }\n \n // Filter to new chunks only\n const newChunks = allChunks.filter((chunk) => !existingHashes.has(chunk.id));\n progress.newChunks = newChunks.length;\n progress.skippedChunks = allChunks.length - newChunks.length;\n reportProgress();\n\n // Phase 4: Embed new chunks\n progress.phase = 'embedding';\n reportProgress();\n \n const embeddingModel = getEmbeddingModel();\n let failedChunks = 0;\n \n // Process in batches (parallelized with a worker pool)\n const batchSize = parsePositiveInt(process.env.SPARKECODER_INDEX_BATCH_SIZE, EMBEDDING_BATCH_SIZE);\n const concurrency = parsePositiveInt(process.env.SPARKECODER_INDEX_CONCURRENCY, EMBEDDING_CONCURRENCY);\n const maxRetries = parsePositiveInt(process.env.SPARKECODER_INDEX_RETRIES, EMBEDDING_RETRIES);\n const totalBatches = Math.ceil(newChunks.length / batchSize);\n console.log(\n `[indexer] Starting embedding: ${newChunks.length} chunks in ${totalBatches} batches (batchSize=${batchSize}, concurrency=${concurrency}, retries=${maxRetries})`\n );\n \n const batches = newChunks.reduce<Array<{ batchNum: number; batch: Chunk[] }>>((acc, chunk, index) => {\n if (index % batchSize === 0) {\n acc.push({\n batchNum: Math.floor(index / batchSize) + 1,\n batch: newChunks.slice(index, index + batchSize),\n });\n }\n return acc;\n }, []);\n\n const processBatch = async (batchNum: number, batch: Chunk[]) => {\n console.log(`[indexer] Batch ${batchNum}/${totalBatches}: embedding ${batch.length} chunks...`);\n const texts = batch.map((chunk) => ({\n id: chunk.id,\n text: chunk.text,\n document: {\n filePath: chunk.metadata.filePath,\n startLine: chunk.metadata.startLine,\n endLine: chunk.metadata.endLine,\n language: chunk.metadata.language,\n chunkType: chunk.metadata.chunkType,\n symbolName: chunk.metadata.symbolName,\n contentHash: chunk.contentHash,\n },\n }));\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const batchStartTime = Date.now();\n try {\n const result = await client.embeddings.createAndWait(texts, {\n namespace,\n embeddingModel,\n });\n \n const embedTime = Date.now() - batchStartTime;\n console.log(`[indexer] Batch ${batchNum}: embed completed in ${embedTime}ms - processed: ${result.processedCount}, failed: ${result.failedCount}`);\n \n failedChunks += result.failedCount;\n \n // Record successful chunks in local DB (batch upsert)\n const successfulChunks = batch.filter(\n (chunk) => !result.errors?.find((e) => e.id === chunk.id)\n );\n \n if (successfulChunks.length > 0) {\n console.log(`[indexer] Batch ${batchNum}: recording ${successfulChunks.length} chunks to DB...`);\n const dbStartTime = Date.now();\n \n await indexedChunkQueries.batchUpsert(db, successfulChunks.map((chunk) => ({\n id: chunk.id,\n contentHash: chunk.contentHash,\n filePath: chunk.metadata.filePath,\n repoNamespace: namespace,\n startLine: chunk.metadata.startLine,\n endLine: chunk.metadata.endLine,\n language: chunk.metadata.language,\n })));\n \n const dbTime = Date.now() - dbStartTime;\n console.log(`[indexer] Batch ${batchNum}: DB batch upsert completed in ${dbTime}ms`);\n }\n \n if (result.errors?.length) {\n for (const err of result.errors) {\n const chunk = batch.find((c) => c.id === err.id);\n if (chunk) {\n errors.push({ file: chunk.metadata.filePath, error: err.error });\n }\n }\n } else if (result.failedCount > 0) {\n errors.push({ file: `batch ${batchNum}`, error: `Embedding failed for ${result.failedCount} chunks (no error details returned)` });\n }\n \n return;\n } catch (error) {\n const errorMsg = formatError(error);\n console.error(`[indexer] Batch ${batchNum}: ERROR (attempt ${attempt + 1}/${maxRetries + 1}) - ${errorMsg}`);\n if (attempt >= maxRetries) {\n failedChunks += batch.length;\n errors.push({ file: `batch ${batchNum}`, error: errorMsg });\n return;\n }\n await sleep(500 * (attempt + 1));\n } finally {\n reportProgress();\n }\n }\n };\n\n let nextBatchIndex = 0;\n const workerCount = Math.min(concurrency, batches.length);\n const workers = Array.from({ length: workerCount }, async () => {\n while (nextBatchIndex < batches.length) {\n const currentIndex = nextBatchIndex;\n nextBatchIndex += 1;\n const { batchNum, batch } = batches[currentIndex];\n await processBatch(batchNum, batch);\n }\n });\n\n await Promise.all(workers);\n \n console.log(`[indexer] Embedding complete. Updating index status...`);\n\n // Update index status\n try {\n console.log(`[indexer] Calling indexStatusQueries.upsert with totalChunks: ${allChunks.length}`);\n await indexStatusQueries.upsert(db, {\n id: namespace,\n repoNamespace: namespace,\n totalChunks: allChunks.length,\n lastFullIndex: options.force ? new Date() : undefined,\n lastIncrementalIndex: new Date(),\n });\n console.log(`[indexer] Index status updated successfully`);\n } catch (statusError) {\n console.error(`[indexer] Failed to update index status:`, statusError);\n throw statusError;\n }\n\n // Phase 5: Done\n progress.phase = 'done';\n reportProgress();\n\n return {\n success: true,\n namespace,\n totalFiles: files.length,\n totalChunks: allChunks.length,\n newChunks: newChunks.length - failedChunks,\n skippedChunks: progress.skippedChunks,\n failedChunks,\n duration: Date.now() - startTime,\n errors,\n };\n } finally {\n await closeVectorClient();\n }\n}\n\n/**\n * Get the index status for a repository\n */\nexport async function getIndexStatus(workingDirectory: string): Promise<IndexStatus> {\n const config = getConfig();\n const namespace = await getRepoNamespace(\n workingDirectory,\n config.resolvedVectorGateway.namespace\n );\n \n const isConfigured = config.resolvedRemoteServer.isConfigured;\n \n if (!namespace) {\n return {\n namespace: '',\n totalChunks: 0,\n lastFullIndex: null,\n lastIncrementalIndex: null,\n isConfigured,\n };\n }\n \n try {\n const db = getDb();\n const status = await indexStatusQueries.get(db, namespace);\n \n if (!status) {\n return {\n namespace,\n totalChunks: 0,\n lastFullIndex: null,\n lastIncrementalIndex: null,\n isConfigured,\n };\n }\n \n return {\n namespace,\n totalChunks: status.totalChunks ?? 0,\n lastFullIndex: status.lastFullIndex ?? null,\n lastIncrementalIndex: status.lastIncrementalIndex ?? null,\n isConfigured,\n };\n } catch {\n return {\n namespace,\n totalChunks: 0,\n lastFullIndex: null,\n lastIncrementalIndex: null,\n isConfigured,\n };\n }\n}\n\n/**\n * Check if an index exists for a repository\n */\nexport async function checkIndexExists(workingDirectory: string): Promise<boolean> {\n const status = await getIndexStatus(workingDirectory);\n return status.totalChunks > 0;\n}\n","import { ToolSet } from 'ai';\nimport { createBashTool, BashToolOptions, BashToolProgress } from './bash.js';\nimport { createReadFileTool, ReadFileToolOptions } from './read-file.js';\nimport { createWriteFileTool, WriteFileToolOptions, WriteFileProgress } from './write-file.js';\nimport { createTodoTool, TodoToolOptions } from './todo.js';\nimport { createLoadSkillTool, LoadSkillToolOptions } from './load-skill.js';\nimport { createLinterTool, LinterToolOptions } from './linter.js';\nimport { createSearchTool, SearchToolOptions, SearchToolProgress } from './search.js';\nimport { createSemanticSearchTool, SemanticSearchToolOptions, SemanticSearchResult } from './semantic-search.js';\nimport { isVectorGatewayConfigured, checkIndexExists } from '../semantic/index.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 /** Called when write_file has progress to report (for streaming content) */\n onWriteFileProgress?: (progress: WriteFileProgress) => void;\n /** Called when explore_agent tool has progress to report (subagent steps) */\n onSearchProgress?: (progress: SearchToolProgress) => void;\n /** Enable LSP diagnostics for file edits (default: true) */\n enableLSP?: boolean;\n /** Enable semantic search if configured (default: true) */\n enableSemanticSearch?: boolean;\n}\n\n/**\n * Create all tools for an agent session\n * Note: This is now async to support checking semantic search availability\n */\nexport async function createTools(options: CreateToolsOptions): Promise<ToolSet> {\n const tools: ToolSet = {\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 enableLSP: options.enableLSP ?? true,\n onProgress: options.onWriteFileProgress,\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 linter: createLinterTool({\n workingDirectory: options.workingDirectory,\n }),\n\n explore_agent: createSearchTool({\n sessionId: options.sessionId,\n workingDirectory: options.workingDirectory,\n onProgress: options.onSearchProgress,\n }),\n };\n\n // Conditionally add semantic_search if configured and index exists\n if (options.enableSemanticSearch !== false) {\n try {\n if (isVectorGatewayConfigured()) {\n const hasIndex = await checkIndexExists(options.workingDirectory);\n if (hasIndex) {\n tools.semantic_search = createSemanticSearchTool({\n workingDirectory: options.workingDirectory,\n });\n }\n }\n } catch {\n // Silently skip semantic search if there are any issues\n }\n }\n\n return tools;\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';\nexport { createLinterTool } from './linter.js';\nexport { createSearchTool } from './search.js';\nexport { createSemanticSearchTool } from './semantic-search.js';\n\n// Export types\nexport type { BashToolOptions, BashToolProgress } from './bash.js';\nexport type { ReadFileToolOptions } from './read-file.js';\nexport type { WriteFileToolOptions, WriteFileProgress } from './write-file.js';\nexport type { TodoToolOptions } from './todo.js';\nexport type { LoadSkillToolOptions } from './load-skill.js';\nexport type { LinterToolOptions } from './linter.js';\nexport type { SearchToolOptions, SearchToolProgress } from './search.js';\nexport type { SemanticSearchToolOptions, SemanticSearchResult } from './semantic-search.js';","import { generateText, type ModelMessage as AIModelMessage } from 'ai';\nimport { resolveModel } from './model.js';\nimport { messageQueries, ModelMessage } from '../db/index.js';\nimport { calculateContextSize } from '../utils/truncate.js';\nimport { createSummaryPrompt } from './prompts.js';\nimport { getConfig } from '../config/index.js';\nimport { sanitizeModelMessages } from '../utils/sanitize-messages.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 * Includes self-repair: if messages from the database have been corrupted\n * (e.g., Date objects in tool outputs from parseDates), they are automatically\n * sanitized to conform to the AI SDK's ModelMessage schema.\n */\n async getMessages(): Promise<AIModelMessage[]> {\n let modelMessages = (await messageQueries.getModelMessages(this.sessionId)) as AIModelMessage[];\n\n // Sanitize messages to ensure they conform to AI SDK schema.\n // This catches and repairs corruption from the database layer\n // (e.g., parseDates converting date strings to Date objects in tool outputs).\n modelMessages = sanitizeModelMessages(modelMessages) 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: resolveModel(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 * Content can be a string or an array of content parts (for messages with images/files)\n */\n async addUserMessage(content: string | Array<{ type: string; text?: string; image?: string; data?: string; mediaType?: string }>): Promise<void> {\n const userMessage: ModelMessage = {\n role: 'user',\n content: content as any,\n };\n await 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 async addResponseMessages(messages: AIModelMessage[]): Promise<void> {\n await messageQueries.addMany(this.sessionId, messages as ModelMessage[]);\n }\n\n /**\n * Get current context statistics\n */\n async getStats(): Promise<{ messageCount: number; contextChars: number; hasSummary: boolean }> {\n const messages = (await 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 async clear(): Promise<void> {\n await messageQueries.deleteBySession(this.sessionId);\n this.summary = null;\n }\n}\n","import os from 'node:os';\nimport {\n loadAllSkillsFromDiscovered,\n getGlobMatchedSkills,\n loadAgentsMd,\n formatSkillsForContext,\n formatAlwaysLoadedSkills,\n formatGlobMatchedSkills,\n formatAgentsMdContent,\n} from '../skills/index.js';\nimport { todoQueries, TodoItem } from '../db/index.js';\nimport { DiscoveredSkills } from '../config/types.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 discoveredSkills?: DiscoveredSkills;\n activeFiles?: string[];\n customInstructions?: string;\n}): Promise<string> {\n const {\n workingDirectory,\n skillsDirectories,\n sessionId,\n discoveredSkills,\n activeFiles = [],\n customInstructions,\n } = options;\n\n // Load skills using the enhanced system if discoveredSkills is provided\n let alwaysLoadedContent = '';\n let globMatchedContent = '';\n let agentsMdContent = '';\n let onDemandSkillsContext = '';\n\n if (discoveredSkills) {\n // Use the new enhanced skill loading\n const { always, onDemand, all } = await loadAllSkillsFromDiscovered(discoveredSkills);\n\n // Format always-loaded skills\n alwaysLoadedContent = formatAlwaysLoadedSkills(always);\n\n // Format on-demand skills for context\n onDemandSkillsContext = formatSkillsForContext(onDemand);\n\n // Load AGENTS.md if present\n const agentsMd = await loadAgentsMd(discoveredSkills.agentsMdPath);\n agentsMdContent = formatAgentsMdContent(agentsMd);\n\n // Load glob-matched skills based on active files\n if (activeFiles.length > 0) {\n const globMatched = await getGlobMatchedSkills(all, activeFiles, workingDirectory);\n globMatchedContent = formatGlobMatchedSkills(globMatched);\n }\n } else {\n // Legacy fallback: just load skills from directories\n const { loadAllSkills } = await import('../skills/index.js');\n const skills = await loadAllSkills(skillsDirectories);\n onDemandSkillsContext = formatSkillsForContext(skills);\n }\n\n // Load current todos\n const todos = await 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- **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)\n- **todo**: Manage your task list to track progress on complex operations\n- **load_skill**: Load specialized knowledge documents for specific tasks\n- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning\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\nTerminal output is stored in the global SparkECoder data directory. Use the \\`tail\\` option to read recent output.\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- After making changes, use the \\`linter\\` tool to check for type errors and lint issues\n- The \\`write_file\\` tool automatically shows lint errors in its output for TypeScript/JavaScript files\n- If the user asks to write/create a file, always use \\`write_file\\` rather than printing the full contents\n- If the user requests a file but does not provide a path, choose a sensible default (e.g. \\`index.html\\`) and proceed\n- For large content (hundreds of lines), avoid placing it in chat output; write to a file instead\n\n### Linter Tool\nThe linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:\n\\`\\`\\`\nlinter({}) // Check all recently edited files\nlinter({ paths: [\"src/app.ts\"] }) // Check specific files\nlinter({ paths: [\"src/\"] }) // Check all files in a directory\n\\`\\`\\`\nUse this proactively after making code changes to catch errors early.\n\n### Searching and Exploration\n\n**Choose the right search approach:**\n\n1. **Use the \\`explore_agent\\` tool (Explore agent)** for:\n - Semantic/exploratory questions: \"How does authentication work?\", \"Where is user data processed?\"\n - Finding code by meaning or concept, not exact text\n - Understanding how features are implemented across multiple files\n - Exploring unfamiliar parts of the codebase\n - Questions like \"where\", \"how\", \"what does X do\"\n \n The Explore agent is a mini-agent that intelligently explores the codebase, reads relevant files, and returns a summary of what it found. It's best for understanding and discovery.\n\n2. **Use direct commands (grep/rg, find)** for:\n - Exact string matches: \\`rg \"functionName\"\\`, \\`rg \"class MyClass\"\\`\n - Finding files by name: \\`find . -name \"*.config.ts\"\\`\n - Simple pattern matching when you know exactly what you're looking for\n - Counting occurrences or listing all matches\n\n**Examples:**\n- \"Where is the API authentication handled?\" → Use \\`explore_agent\\` tool\n- \"Find all usages of getUserById\" → Use \\`rg \"getUserById\"\\`\n- \"How does the payment flow work?\" → Use \\`explore_agent\\` tool\n- \"Find files named config\" → Use \\`find . -name \"*config*\"\\`\n\n${searchInstructions}\n\n###Follow 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### Follow these rules to be a good agent for the user:\n\n1. Understand first - Read relevant files before making any changes. Use the \\`explore_agent\\` tool for exploratory questions about how things work, and direct searches (grep/rg) for finding exact strings or file names.\n2. Plan for complexity - If the task involves 3+ steps or has meaningful trade-offs, create a todo list to track progress before implementing.\n3. Use the right tools - Have specialized tools for reading files, editing code, semantic search via subagents, and running terminal commands. Prefer these over raw shell commands.\n4. Work efficiently - When need to do multiple independent things (like reading several files), do them in parallel rather than one at a time.\n5. Be direct - Focus on technical accuracy rather than validation. If see issues with an approach or need clarification, say so.\n6. Verify my work - After making changes, check for linter errors and fix any introduced.\n7. Respect boundaries - Only commit code when explicitly asked, avoid creating unnecessary files, and don't make assumptions about things uncertain about.\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${agentsMdContent}\n\n${alwaysLoadedContent}\n\n${globMatchedContent}\n\n## On-Demand Skills\n${onDemandSkillsContext}\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","/**\n * Message sanitization for AI SDK ModelMessage compatibility.\n * \n * Ensures messages retrieved from the database conform to the AI SDK's\n * ModelMessage[] schema before being passed to streamText()/generateText().\n * \n * Handles two classes of issues:\n * \n * 1. SCHEMA CORRUPTION: The remote database client's `parseDates()` function\n * recursively converts ISO date strings (like `createdAt`) inside tool result\n * outputs to JavaScript Date objects. The AI SDK's jsonValueSchema only accepts\n * JSON primitives — Date objects are rejected, causing AI_InvalidPromptError.\n * \n * 2. CONSECUTIVE SAME-ROLE MESSAGES: If multiple user messages are saved to the\n * database without an assistant response between them (e.g., user sends two\n * messages quickly, or the previous stream errored before producing a response),\n * the Anthropic API rejects consecutive same-role messages. This module merges\n * them into a single message.\n * \n * This module provides a safety net that catches and repairs any corrupted\n * messages so the agent can self-heal even if the database layer returns\n * unexpected data.\n */\n\nimport { modelMessageSchema, type ModelMessage } from 'ai';\n\n/**\n * Recursively convert Date objects to ISO strings within a value.\n * This reverses any accidental Date conversions from parseDates.\n */\nfunction convertDatesToStrings(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.map(convertDatesToStrings);\n if (typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n result[k] = convertDatesToStrings(v);\n }\n return result;\n }\n return value;\n}\n\n/**\n * Sanitize a single ModelMessage so it passes AI SDK schema validation.\n * \n * Fixes known corruption patterns:\n * - Date objects inside tool result output (from parseDates)\n * - Date objects inside tool call input\n * - Date objects inside user/assistant content parts\n */\nfunction sanitizeMessage(msg: unknown): unknown {\n if (msg === null || msg === undefined || typeof msg !== 'object') return msg;\n \n const message = msg as Record<string, unknown>;\n \n // Only process messages with a recognized role\n if (!message.role || typeof message.role !== 'string') return msg;\n \n // Deep-convert any Date objects back to ISO strings\n return convertDatesToStrings(message);\n}\n\n/**\n * Merge the content of two user messages into a single content value.\n * \n * Rules:\n * - string + string → joined with \"\\n\\n\"\n * - array + array → concatenated (deduplicating text parts)\n * - string + array → string converted to text part, then concatenated\n * - array + string → string converted to text part, then appended\n */\nfunction mergeUserContent(\n a: unknown,\n b: unknown,\n): string | Array<Record<string, unknown>> {\n const aIsString = typeof a === 'string';\n const bIsString = typeof b === 'string';\n\n // Both strings: simple join\n if (aIsString && bIsString) {\n return `${a}\\n\\n${b}`;\n }\n\n // Normalise both to arrays\n const aParts: Array<Record<string, unknown>> = aIsString\n ? [{ type: 'text', text: a }]\n : Array.isArray(a)\n ? (a as Array<Record<string, unknown>>)\n : [];\n\n const bParts: Array<Record<string, unknown>> = bIsString\n ? [{ type: 'text', text: b }]\n : Array.isArray(b)\n ? (b as Array<Record<string, unknown>>)\n : [];\n\n return [...aParts, ...bParts];\n}\n\n/**\n * Merge consecutive same-role messages in the array.\n * \n * The Anthropic API (and some other providers) reject message arrays where\n * two consecutive messages share the same role. This can happen when:\n * - The user sends two messages before the agent responds\n * - The previous agent stream errored/aborted before producing a response\n * - Network issues cause duplicate message saves\n * \n * For user messages: merges content (text joined with newlines, parts concatenated)\n * For assistant messages: merges content parts into a single array\n * For tool messages: concatenates content arrays\n */\nfunction mergeConsecutiveSameRole(messages: ModelMessage[]): ModelMessage[] {\n if (messages.length <= 1) return messages;\n\n const merged: ModelMessage[] = [];\n\n for (const msg of messages) {\n const prev = merged[merged.length - 1];\n\n if (!prev || (prev as any).role !== (msg as any).role) {\n // Different role or first message — keep as-is\n merged.push(msg);\n continue;\n }\n\n // Same role as previous — merge\n const role = (msg as any).role as string;\n\n if (role === 'user') {\n const mergedContent = mergeUserContent((prev as any).content, (msg as any).content);\n merged[merged.length - 1] = { role: 'user', content: mergedContent } as any as ModelMessage;\n console.warn('[sanitize-messages] Merged consecutive user messages');\n } else if (role === 'assistant') {\n // Normalise both to arrays and concatenate\n const prevParts = typeof (prev as any).content === 'string'\n ? [{ type: 'text', text: (prev as any).content }]\n : Array.isArray((prev as any).content)\n ? (prev as any).content\n : [];\n const curParts = typeof (msg as any).content === 'string'\n ? [{ type: 'text', text: (msg as any).content }]\n : Array.isArray((msg as any).content)\n ? (msg as any).content\n : [];\n merged[merged.length - 1] = { role: 'assistant', content: [...prevParts, ...curParts] } as any as ModelMessage;\n console.warn('[sanitize-messages] Merged consecutive assistant messages');\n } else if (role === 'tool') {\n // Tool messages always have array content — concatenate\n const prevContent = Array.isArray((prev as any).content) ? (prev as any).content : [];\n const curContent = Array.isArray((msg as any).content) ? (msg as any).content : [];\n merged[merged.length - 1] = { role: 'tool', content: [...prevContent, ...curContent] } as any as ModelMessage;\n console.warn('[sanitize-messages] Merged consecutive tool messages');\n } else {\n // Unknown role — just push, don't try to merge\n merged.push(msg);\n }\n }\n\n return merged;\n}\n\n/**\n * Validate and sanitize an array of ModelMessage objects.\n * \n * Performs two passes:\n * 1. Schema repair — fixes individual messages that fail AI SDK validation\n * (Date objects, missing type wrappers, etc.)\n * 2. Sequence repair — merges consecutive same-role messages that providers\n * like Anthropic would reject\n * \n * Returns the original messages if they're all valid and properly sequenced,\n * or repaired copies if any had issues. Logs warnings for any messages that\n * needed repair so the issue is visible in server logs.\n */\nexport function sanitizeModelMessages(messages: ModelMessage[]): ModelMessage[] {\n // === Pass 1: Schema repair ===\n // Fast path: try validating the whole array first\n let allValid = true;\n for (const msg of messages) {\n try {\n modelMessageSchema.parse(msg);\n } catch {\n allValid = false;\n break;\n }\n }\n \n let result: ModelMessage[];\n \n if (allValid) {\n result = messages;\n } else {\n // Slow path: sanitize each message individually\n console.warn('[sanitize-messages] Detected invalid messages, attempting self-repair...');\n \n const sanitized: ModelMessage[] = [];\n let repairCount = 0;\n \n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n \n // Check if this specific message is valid\n try {\n modelMessageSchema.parse(msg);\n sanitized.push(msg);\n continue;\n } catch {\n // Needs repair\n }\n \n // Strategy 1: Convert Date objects to ISO strings\n const fixed = sanitizeMessage(msg) as ModelMessage;\n try {\n modelMessageSchema.parse(fixed);\n sanitized.push(fixed);\n repairCount++;\n console.warn(`[sanitize-messages] Repaired message ${i} (role=${(msg as any).role}) - converted Date objects to strings`);\n continue;\n } catch {\n // Strategy 1 failed\n }\n \n // Strategy 2: For tool messages, try wrapping raw output in { type: 'json', value: ... }\n // This handles cases where tool output was stored in legacy format\n if ((msg as any).role === 'tool' && Array.isArray((msg as any).content)) {\n const fixedContent = ((msg as any).content as any[]).map((part: any) => {\n if (part.type === 'tool-result' && part.output !== undefined) {\n const output = convertDatesToStrings(part.output);\n // If output doesn't have a recognized type discriminator, wrap it\n if (output && typeof output === 'object' && !(output as any).type) {\n return { ...part, output: { type: 'json', value: output } };\n }\n // If output has a type but it's not a recognized discriminator, wrap it\n const knownTypes = ['text', 'json', 'execution-denied', 'error-text', 'error-json', 'content'];\n if (output && typeof output === 'object' && !knownTypes.includes((output as any).type)) {\n return { ...part, output: { type: 'json', value: output } };\n }\n return { ...part, output };\n }\n return convertDatesToStrings(part);\n });\n \n const wrappedMsg = { ...(msg as any), content: fixedContent } as ModelMessage;\n try {\n modelMessageSchema.parse(wrappedMsg);\n sanitized.push(wrappedMsg);\n repairCount++;\n console.warn(`[sanitize-messages] Repaired message ${i} (role=tool) - wrapped raw output in json type`);\n continue;\n } catch {\n // Strategy 2 failed\n }\n }\n \n // Strategy 3: Last resort - include the message as-is and let it fail\n // downstream with a better error context. This is better than silently\n // dropping messages which could corrupt conversation state.\n console.error(\n `[sanitize-messages] Could not repair message ${i} (role=${(msg as any).role}). ` +\n `Message will be included as-is. Content keys: ${JSON.stringify(Object.keys(msg as any))}`,\n );\n sanitized.push(msg);\n }\n \n if (repairCount > 0) {\n console.warn(`[sanitize-messages] Self-repair complete: fixed ${repairCount}/${messages.length} messages`);\n }\n \n result = sanitized;\n }\n\n // === Pass 2: Sequence repair ===\n // Merge consecutive same-role messages (Anthropic rejects these)\n result = mergeConsecutiveSameRole(result);\n\n return result;\n}\n"],"mappings":"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","names":["readFile","resolve","extname","relative","existsSync","streamText","generateText","tool","stepCountIs","z","nanoid","z","exec","promisify","existsSync","mkdirSync","join","existsSync","mkdirSync","join","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","extname","dirname","existsSync","resolve","dirname","exec","promisify","execAsync","readFile","existsSync","resolve","extname","dirname","extname","z","tool","isAbsolute","resolve","relative","existsSync","dirname","mkdir","writeFile","readFile","tool","z","z","tool","tool","z","z","tool","tool","z","resolve","relative","isAbsolute","extname","existsSync","readdir","stat","z","readdir","resolve","extname","tool","isAbsolute","existsSync","stat","relative","tool","z","nanoid","nanoid","resolve","tool","z","exec","promisify","readFile","stat","readdir","resolve","relative","isAbsolute","existsSync","execAsync","promisify","exec","MAX_OUTPUT_CHARS","MAX_FILE_SIZE","tool","z","resolve","isAbsolute","existsSync","stat","readFile","relative","readdir","tool","z","tool","z","existsSync","readFileSync","join","minimatch","extname","basename","remoteServerUrl","authKey","readFileSync","relative","minimatch","MAX_FILE_SIZE","z","tool","minimatch","join","existsSync","readFileSync","generateText","platform","loadAllSkills","generateText","streamText","stepCountIs","generateText","tool","z","nanoid","resolve","exec"]}
|