@24klynx/cli 0.1.1 → 0.1.2
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/{config-Des0z-k9.mjs → config-Braj-aBw.mjs} +3 -3
- package/dist/config-Braj-aBw.mjs.map +1 -0
- package/dist/config-D9DbomLt.mjs +2 -0
- package/dist/context-BmZ8VEan.mjs.map +1 -1
- package/dist/{env-CeeZcoDI.mjs → env-jKIO_4zX.mjs} +2 -2
- package/dist/env-jKIO_4zX.mjs.map +1 -0
- package/dist/{headless-launcher-I8NWyD6k.mjs → headless-launcher-CnSrw3bm.mjs} +4 -4
- package/dist/headless-launcher-CnSrw3bm.mjs.map +1 -0
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +9 -9
- package/dist/index.mjs.map +1 -1
- package/dist/{process-lifecycle-Dg6n2QS-.mjs → process-lifecycle-kdV53L3o.mjs} +9 -9
- package/dist/process-lifecycle-kdV53L3o.mjs.map +1 -0
- package/package.json +10 -10
- package/dist/config-D-xVXTXi.mjs +0 -2
- package/dist/config-Des0z-k9.mjs.map +0 -1
- package/dist/env-CeeZcoDI.mjs.map +0 -1
- package/dist/headless-launcher-I8NWyD6k.mjs.map +0 -1
- package/dist/process-lifecycle-Dg6n2QS-.mjs.map +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { n as loadConfig } from "./config-
|
|
2
|
-
import { migrate, openDatabase, resolvePaths } from "@
|
|
3
|
-
import { createSessionManager } from "@
|
|
4
|
-
import { createMcpAuthHandler, createMemoryWriteHandler, createSendMessageHandler, createToolRegistry, injectTaskManager, mcpAuthDescriptor, memoryWriteDescriptor, registerBuiltinTools, sendMessageDescriptor } from "@
|
|
5
|
-
import { PredictiveLoader, createHookRegistry, createManifestRegistry, createPluginLoader } from "@
|
|
6
|
-
import { createMcpManager, createMemoryManager, createQueryEngine, createSkillRegistry, createSkillToolHandler, createTaskManager, getBaseSystemPrompt } from "@
|
|
7
|
-
import { createDenialTracker, createRuleEngine, createRulesLoader, loadFromDisk } from "@
|
|
1
|
+
import { n as loadConfig } from "./config-Braj-aBw.mjs";
|
|
2
|
+
import { migrate, openDatabase, resolvePaths } from "@24klynx/core";
|
|
3
|
+
import { createSessionManager } from "@24klynx/session";
|
|
4
|
+
import { createMcpAuthHandler, createMemoryWriteHandler, createSendMessageHandler, createToolRegistry, injectTaskManager, mcpAuthDescriptor, memoryWriteDescriptor, registerBuiltinTools, sendMessageDescriptor } from "@24klynx/tools";
|
|
5
|
+
import { PredictiveLoader, createHookRegistry, createManifestRegistry, createPluginLoader } from "@24klynx/plugins";
|
|
6
|
+
import { createMcpManager, createMemoryManager, createQueryEngine, createSkillRegistry, createSkillToolHandler, createTaskManager, getBaseSystemPrompt } from "@24klynx/agent";
|
|
7
|
+
import { createDenialTracker, createRuleEngine, createRulesLoader, loadFromDisk } from "@24klynx/permissions";
|
|
8
8
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
|
-
import { createChannelRegistry, createFeishuAdapter } from "@
|
|
12
|
+
import { createChannelRegistry, createFeishuAdapter } from "@24klynx/channels";
|
|
13
13
|
//#region src/mcp-loader.ts
|
|
14
14
|
/**
|
|
15
15
|
* MCP configuration loader — reads MCP server configurations
|
|
@@ -781,4 +781,4 @@ function startForceExitTimer() {
|
|
|
781
781
|
//#endregion
|
|
782
782
|
export { endSync as a, withSync as c, runPhase2WithContext as d, bootstrap as f, beginSync as i, runPhase1 as l, onCleanup as n, enterFullscreen as o, uninstallProcessLifecycle as r, exitFullscreen as s, installProcessLifecycle as t, runPhase2 as u };
|
|
783
783
|
|
|
784
|
-
//# sourceMappingURL=process-lifecycle-
|
|
784
|
+
//# sourceMappingURL=process-lifecycle-kdV53L3o.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-lifecycle-kdV53L3o.mjs","names":[],"sources":["../src/mcp-loader.ts","../src/bootstrap.ts","../src/startup.ts","../src/terminal-mode.ts","../src/process-lifecycle.ts"],"sourcesContent":["/**\n * MCP configuration loader — reads MCP server configurations\n * from Lynx settings files (3‑layer merge: global → project → local).\n *\n * Settings layers (later overrides earlier for same‑named servers):\n * 1. ~/.lynx/settings.json — global\n * 2. .lynx/settings.json — project (committed)\n * 3. .lynx/settings.local.json — project local (gitignored)\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolvePaths } from \"@24klynx/core\";\nimport type { McpServerConfig } from \"@24klynx/agent\";\nimport { join } from \"node:path\";\n\n// ── Types ────────────────────────────────────────────\n\ninterface SettingsFile {\n mcpServers?: McpServerConfig[];\n enableAllProjectMcpServers?: boolean;\n enabledMcpjsonServers?: string[];\n disabledMcpjsonServers?: string[];\n}\n\n/** Where we found the config for a server. */\nexport type McpConfigSource = \"global\" | \"project\" | \"project-local\";\n\nexport interface McpServerEntry {\n config: McpServerConfig;\n source: McpConfigSource;\n}\n\nexport interface McpLoadResult {\n /** Merged server configs (project overrides global for same name). */\n servers: McpServerConfig[];\n /** Per‑server metadata, keyed by server name. */\n entries: Map<string, McpServerEntry>;\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/**\n * Try to read and parse a JSON file. Returns undefined if the file\n * does not exist or is malformed.\n */\nfunction tryReadJson<T>(filePath: string): T | undefined {\n if (!existsSync(filePath)) return undefined;\n try {\n return JSON.parse(readFileSync(filePath, \"utf-8\")) as T;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Find the project root by walking up from cwd looking for `.lynx/`.\n * Returns undefined if no project root is found.\n */\nfunction findProjectRoot(startDir: string): string | undefined {\n let dir = startDir;\n for (let i = 0; i < 50; i++) {\n if (existsSync(join(dir, \".lynx\"))) return dir;\n const parent = join(dir, \"..\");\n if (parent === dir) return undefined;\n dir = parent;\n }\n return undefined;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Load MCP server configurations from all settings layers.\n *\n * Merge order: global → project → project‑local.\n * Same‑named servers in later layers override earlier ones entirely.\n */\nexport function loadMcpConfigs(workspaceDir?: string): McpLoadResult {\n const paths = resolvePaths();\n\n // 1. Global settings\n const globalSettings = tryReadJson<SettingsFile>(paths.settingsFile);\n\n // 2. Project settings\n const projectRoot = workspaceDir ? findProjectRoot(workspaceDir) : findProjectRoot(process.cwd());\n const projectSettings = projectRoot\n ? tryReadJson<SettingsFile>(join(projectRoot, \".lynx\", \"settings.json\"))\n : undefined;\n\n // 3. Project‑local settings\n const localSettings = projectRoot\n ? tryReadJson<SettingsFile>(join(projectRoot, \".lynx\", \"settings.local.json\"))\n : undefined;\n\n // Merge: global → project → local (later wins)\n const merged = new Map<string, McpServerEntry>();\n\n function addServers(servers: McpServerConfig[] | undefined, source: McpConfigSource): void {\n if (!servers) return;\n for (const server of servers) {\n merged.set(server.name, { config: server, source });\n }\n }\n\n addServers(globalSettings?.mcpServers, \"global\");\n addServers(projectSettings?.mcpServers, \"project\");\n addServers(localSettings?.mcpServers, \"project-local\");\n\n return {\n servers: Array.from(merged.values()).map((e) => e.config),\n entries: merged,\n };\n}\n\n/**\n * Convenience: load and validate MCP configs for bootstrap.\n *\n * Returns an array of valid McpServerConfig objects, filtering out\n * entries that have neither a `command` nor a `url`.\n */\nexport function loadAndValidateMcpConfigs(workspaceDir?: string): McpServerConfig[] {\n const result = loadMcpConfigs(workspaceDir);\n return result.servers.filter((s) => {\n const isValid = !!(s.command || s.url);\n if (!isValid) {\n // Logged at trace level — most callers don't need to see this\n }\n return isValid;\n });\n}\n","/**\n * Bootstrap — the single assembly point for all Lynx dependencies.\n *\n * Every service is created via factory functions and wired together\n * here. No DI container — dependencies are passed explicitly.\n *\n * Order: infrastructure → core services → engine → TUI bridge.\n */\n\nimport type { Database } from \"@24klynx/core\";\nimport { openDatabase, migrate } from \"@24klynx/core\";\nimport type { SessionManager } from \"@24klynx/session\";\nimport { createSessionManager } from \"@24klynx/session\";\nimport type { ToolRegistry, ToolHandler, ToolDescriptor } from \"@24klynx/tools\";\nimport {\n createToolRegistry,\n registerBuiltinTools,\n createMemoryWriteHandler,\n memoryWriteDescriptor,\n injectTaskManager,\n mcpAuthDescriptor,\n createMcpAuthHandler,\n sendMessageDescriptor,\n createSendMessageHandler,\n} from \"@24klynx/tools\";\nimport type { LlmProvider } from \"@24klynx/llm\";\nimport type { ManifestRegistry, PluginLoader, HookRegistry } from \"@24klynx/plugins\";\nimport {\n createManifestRegistry,\n createPluginLoader,\n createHookRegistry,\n PredictiveLoader,\n} from \"@24klynx/plugins\";\nimport type {\n QueryEngine,\n AgentConfig,\n SkillRegistry,\n SkillDefinition,\n McpManager,\n McpServerConfig,\n} from \"@24klynx/agent\";\nimport {\n createQueryEngine,\n createSkillRegistry,\n createSkillToolHandler,\n createMcpManager,\n createMemoryManager,\n createTaskManager,\n} from \"@24klynx/agent\";\nimport { getBaseSystemPrompt } from \"@24klynx/agent\";\nimport type { MemoryManager, TaskManager } from \"@24klynx/agent\";\nimport type { RuleEngine } from \"@24klynx/permissions\";\nimport {\n createRuleEngine,\n createRulesLoader,\n createDenialTracker,\n loadFromDisk,\n} from \"@24klynx/permissions\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { randomUUID } from \"node:crypto\";\nimport { loadAndValidateMcpConfigs } from \"./mcp-loader.js\";\nimport { createChannelRegistry, createFeishuAdapter } from \"@24klynx/channels\";\nimport type { ChannelRegistry, FeishuConfig } from \"@24klynx/channels\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Bridge between the agent engine's permission checks and the TUI's permission dialog. */\nexport interface PermissionBridge {\n /**\n * Called by the agent engine before executing a tool.\n * Returns true if the tool is allowed, false if denied.\n * May await user interaction via the TUI dialog.\n */\n requestPermission(\n toolName: string,\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\",\n description: string,\n ): Promise<boolean>;\n /**\n * Called by the TUI's onPermissionReply callback.\n * Resolves the pending permission Promise.\n */\n handleReply(requestId: string, approved: boolean): void;\n /**\n * Called by the TUI after mount to register the dialog trigger.\n * The handler pushes a permission view onto the TUI stack.\n */\n setTuiHandler(\n handler:\n | ((req: {\n requestId: string;\n toolName: string;\n description: string;\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n }) => void)\n | null,\n ): void;\n}\n\nexport interface AppContext {\n db: Database;\n sessionMgr: SessionManager;\n toolRegistry: ToolRegistry;\n pluginRegistry: {\n manifestRegistry: ManifestRegistry;\n loader: PluginLoader;\n hooks: HookRegistry;\n };\n ruleEngine: RuleEngine;\n engine: QueryEngine;\n provider: LlmProvider;\n /** Mutable agent config — model can be changed at runtime without recreating the engine. */\n agentConfig: AgentConfig;\n /** Permission bridge for tool approval flow. */\n permissionBridge: PermissionBridge;\n /** Skill registry for progressive disclosure (built‑in + user skills). */\n skillRegistry: SkillRegistry;\n /** Current skill definitions for prompt injection. */\n skills: SkillDefinition[];\n /** MCP connection manager for external tool servers. */\n mcpManager: McpManager;\n /** Predictive loader — pre‑warms plugin modules during idle time. */\n predictiveLoader: PredictiveLoader;\n /** Channel registry — manages messaging channel adapters (飞书 etc.). */\n channelRegistry: ChannelRegistry;\n /** Memory manager for persistent cross-session recall. */\n memoryManager: MemoryManager;\n /** Persistent memory facts loaded from disk. */\n memoryFacts: string[];\n /** Textual rules loaded from disk for prompt injection. */\n rules: string[];\n /** Destroy all resources (close DB, stop timers, etc.). */\n destroy(): void;\n}\n\nexport interface BootstrapConfig {\n homeDir: string;\n provider: LlmProvider;\n model: string;\n /** Workspace directory (defaults to cwd). Used for loading project-level memory/rules/skills. */\n workspace?: string;\n /** Path to built‑in skills directory (SKILL.md files). */\n skillsDir?: string;\n /** MCP server configurations to connect on startup. */\n mcpServers?: McpServerConfig[];\n /** 飞书 channel configuration (optional — channel not created if omitted). */\n feishu?: FeishuConfig;\n}\n\n// ── Permission bridge factory ─────────────────────────\n\n/** Timeout for auto‑denying a permission request when the user doesn't respond. */\nconst PERMISSION_TIMEOUT_MS = 60_000;\n\ninterface PendingPermission {\n resolve: (approved: boolean) => void;\n timer: NodeJS.Timeout;\n}\n\n/**\n * Create a permission bridge that mediates between the agent engine\n * and the TUI's permission dialog.\n *\n * The bridge uses a Promise Map pattern:\n * 1. Agent calls `requestPermission()` → Promise created + stored\n * 2. TUI handler fires → shows permission dialog\n * 3. User responds → `handleReply()` resolves the Promise\n * 4. Agent resumes (or skips the tool)\n *\n * Safety levels \"Safe\" and \"WorkspaceSafe\" are auto‑allowed without\n * showing the dialog.\n */\nfunction createPermissionBridge(): PermissionBridge {\n const pending = new Map<string, PendingPermission>();\n let tuiHandler:\n | ((req: {\n requestId: string;\n toolName: string;\n description: string;\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n }) => void)\n | null = null;\n\n const bridge: PermissionBridge = {\n async requestPermission(toolName, safety, description): Promise<boolean> {\n // Auto‑allow safe operations\n if (safety === \"Safe\" || safety === \"WorkspaceSafe\") return true;\n\n return new Promise<boolean>((resolve) => {\n const requestId = randomUUID();\n const timer = setTimeout(() => {\n pending.delete(requestId);\n resolve(false);\n }, PERMISSION_TIMEOUT_MS);\n\n pending.set(requestId, { resolve, timer });\n\n // Signal the TUI to show the permission dialog\n if (tuiHandler) {\n tuiHandler({ requestId, toolName, description, safety });\n } else {\n // No TUI attached (headless mode) → auto‑deny\n clearTimeout(timer);\n pending.delete(requestId);\n resolve(false);\n }\n });\n },\n\n handleReply(requestId, approved): void {\n const entry = pending.get(requestId);\n if (entry) {\n clearTimeout(entry.timer);\n pending.delete(requestId);\n entry.resolve(approved);\n }\n },\n\n setTuiHandler(handler): void {\n tuiHandler = handler;\n },\n };\n\n return bridge;\n}\n\n// ── Helpers ────────────────────────────────────────────\n\n/**\n * Load all .md files from a directory as an array of file contents.\n * Silently returns [] if the directory doesn't exist or is unreadable.\n */\nfunction loadTextFilesFromDir(dir: string): string[] {\n const facts: string[] = [];\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return facts;\n }\n for (const entry of entries) {\n if (!entry.endsWith(\".md\")) continue;\n const fullPath = join(dir, entry);\n try {\n if (!statSync(fullPath).isFile()) continue;\n facts.push(readFileSync(fullPath, \"utf-8\"));\n } catch {\n // Skip unreadable files\n }\n }\n return facts;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Bootstrap the entire Lynx application.\n *\n * This is the ONLY place where cross‑package wiring happens.\n * Every other module only talks to its immediate dependencies\n * through explicit factory‑injected interfaces.\n */\nexport function bootstrap(config: BootstrapConfig): AppContext {\n const dbPath = join(config.homeDir, \"state.db\");\n const workspace = config.workspace ?? process.cwd();\n\n // 1. Infrastructure\n const db = openDatabase({ dbPath });\n migrate(db);\n\n // 2. Core services\n const sessionMgr = createSessionManager(db);\n const toolRegistry = createToolRegistry();\n const ruleEngine = createRuleEngine();\n const rulesLoader = createRulesLoader(ruleEngine);\n\n // Register built‑in tools (files, agent, mode, interact)\n registerBuiltinTools(toolRegistry);\n\n // 3. Skills — progressive disclosure system with multi‑directory support\n // Default built‑in skills directory relative to lynx‑agent package\n const skillsDir =\n config.skillsDir ?? join(config.homeDir, \"..\", \"packages\", \"lynx-agent\", \"skills\");\n const skillRegistry = createSkillRegistry(skillsDir);\n\n // Also scan user and project skill directories\n const userSkillsDir = join(homedir(), \".lynx\", \"skills\");\n skillRegistry.addDirectory(userSkillsDir);\n const projectSkillsDir = join(workspace, \".claude\", \"skills\");\n skillRegistry.addDirectory(projectSkillsDir);\n\n const skills = skillRegistry.list();\n\n // Register the Skill tool so the model can request full skill bodies\n const skillToolDescriptor: ToolDescriptor = {\n name: \"Skill\",\n description:\n \"加载技能完整指令。支持 load(加载技能内容)、list(列出所有技能)、\" +\n \"search(搜索技能)、reload(重新加载技能目录)四种操作。\",\n inputSchema: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"load\", \"list\", \"search\", \"reload\"],\n description:\n \"操作类型:load 加载技能内容、list 列出所有技能、search 搜索技能、reload 重新加载\",\n },\n skill: { type: \"string\", description: \"要加载的技能名称(load 操作时使用)\" },\n args: { type: \"string\", description: \"传递给技能的可选参数\" },\n query: { type: \"string\", description: \"搜索关键词(search 操作时使用)\" },\n },\n },\n kind: \"ReadOnly\",\n safety: \"Safe\",\n availability: { type: \"always\" },\n executor: \"Skill\",\n owner: \"core\",\n };\n toolRegistry.register(skillToolDescriptor, createSkillToolHandler(skillRegistry));\n\n // 3a. MCP — connection manager for external tool servers\n const mcpManager = createMcpManager();\n\n // Load MCP servers from settings files (global → project → local)\n const settingsMcpServers = loadAndValidateMcpConfigs();\n\n // Merge programmatic config with settings-loaded configs\n // Programmatic config takes precedence for same‑named servers\n const programmaticServers = config.mcpServers ?? [];\n const settingsOnly = settingsMcpServers.filter(\n (s) => !programmaticServers.some((p) => p.name === s.name),\n );\n const allMcpServers = [...programmaticServers, ...settingsOnly];\n\n // Connect all configured MCP servers (non‑blocking — tools appear when connected)\n for (const serverConfig of allMcpServers) {\n mcpManager.connect(serverConfig).catch(() => {\n // Connection failures are tracked via McpConnection.status;\n // the agent can call reconnect() to retry.\n });\n }\n\n // 3b. Channel registry — messaging channels (飞书 etc.)\n const channelRegistry = createChannelRegistry();\n\n // Register 飞书 adapter if configured\n if (config.feishu?.appId && config.feishu?.appSecret) {\n const feishuAdapter = createFeishuAdapter(config.feishu);\n channelRegistry.register(feishuAdapter);\n }\n\n // Rebuild toolHandlers map to include the Skill tool\n // Keys are tool NAMES (not executors) because loop.ts looks up by call.name\n const toolHandlers = new Map<string, ToolHandler>();\n const allTools = toolRegistry.listAll();\n for (const desc of allTools) {\n try {\n const handler = toolRegistry.resolveExecutor(desc);\n toolHandlers.set(desc.name, handler);\n } catch {\n // Handler not registered — skip (will be reported as \"unknown tool\" at runtime)\n }\n }\n\n // Register MCP dispatcher — each MCP tool gets a handler that forwards calls\n // to the McpManager, which routes to the correct server process.\n const mcpDispatcher: ToolHandler = {\n async handle(invocation, _signal) {\n const result = await mcpManager.callTool(invocation.toolName, invocation.payload);\n return {\n content: result.content,\n success: !result.isError,\n };\n },\n };\n // Register MCP tools — warn on name conflicts instead of silently overwriting\n for (const mcpTool of mcpManager.getAllTools()) {\n if (toolHandlers.has(mcpTool.name)) {\n process.stderr.write(`[lynx] 警告:MCP 工具 \"${mcpTool.name}\" 与现有工具冲突 — 已跳过\\n`);\n continue;\n }\n toolHandlers.set(mcpTool.name, mcpDispatcher);\n }\n\n // Merge MCP tools into the allTools catalog so they are visible to the model.\n // Filter out tools that were skipped due to name conflicts.\n const mcpToolNames = new Set(mcpManager.getAllTools().map((t) => t.name));\n const allToolsWithMcp = [\n ...allTools,\n ...mcpManager.getAllTools().filter((t) => !allTools.some((b) => b.name === t.name)),\n ];\n\n // 4. Plugin system\n const manifestRegistry = createManifestRegistry();\n const hookRegistry = createHookRegistry();\n const pluginLoader = createPluginLoader();\n const pluginRegistry = { manifestRegistry, loader: pluginLoader, hooks: hookRegistry };\n\n // 4a. Predictive loader — pre‑warms plugin modules during Phase 2 idle time\n const predictiveLoader = new PredictiveLoader(async (pluginId: string) => {\n // Dynamic import of plugin modules; pluginId is an npm package name\n // or a file path prefixed with \"file:\" for local extensions\n return import(pluginId);\n });\n\n // 4b. Load permission rules from disk (settings.json + permissions.json)\n loadFromDisk(rulesLoader, workspace);\n\n // 4c. Denial tracker — circuit breaker for consecutive denials\n const denialTracker = createDenialTracker();\n\n // 5. Permission bridge — shared between engine and TUI\n const permissionBridge = createPermissionBridge();\n\n // 5a. Memory management\n const userMemoryDir = join(homedir(), \".lynx\", \"memory\");\n const projectMemoryDir = join(workspace, \".claude\", \"memory\");\n const memoryManager = createMemoryManager(userMemoryDir);\n const memoryFacts = [\n ...(memoryManager\n .list()\n .map((e) => memoryManager.get(e.name)?.content)\n .filter(Boolean) as string[]),\n ...loadTextFilesFromDir(projectMemoryDir),\n ];\n\n // Register memory_write tool — needs MemoryManager injection\n const memoryWriteHandler = createMemoryWriteHandler(memoryManager);\n toolRegistry.register(memoryWriteDescriptor, memoryWriteHandler);\n toolHandlers.set(memoryWriteDescriptor.name, memoryWriteHandler);\n allTools.push(memoryWriteDescriptor);\n\n // Register McpAuthTool — needs McpManager injection for reconnection\n const mcpAuthOps = {\n reconnect: async (serverName: string) => {\n const conn = await mcpManager.reconnect(serverName);\n return { status: conn.status };\n },\n };\n const mcpAuthHandler = createMcpAuthHandler(mcpAuthOps);\n toolRegistry.register(mcpAuthDescriptor, mcpAuthHandler);\n toolHandlers.set(mcpAuthDescriptor.name, mcpAuthHandler);\n allTools.push(mcpAuthDescriptor);\n\n // Register SendMessageTool — needs channel registry injection\n const sendMessageSender = {\n async send(channel: string, content: string, recipients?: string[]) {\n const adapter = channelRegistry.get(channel);\n if (!adapter) throw new Error(`未注册的消息通道:${channel}`);\n const message = {\n id: randomUUID() as unknown as import(\"@24klynx/core\").MessageId,\n role: \"assistant\" as const,\n content: [{ type: \"text\" as const, text: content }],\n timestamp: Date.now(),\n turnIndex: 0,\n };\n const messageId = await adapter.sendMessage(message);\n return { messageId };\n },\n };\n const sendMessageHandler = createSendMessageHandler(sendMessageSender);\n toolRegistry.register(sendMessageDescriptor, sendMessageHandler);\n toolHandlers.set(sendMessageDescriptor.name, sendMessageHandler);\n allTools.push(sendMessageDescriptor);\n\n // 全局用户指令(~/.lynx/LYNX.md)— 优先级最高,在所有项目中生效\n const globalLynxMd = join(homedir(), \".lynx\", \"LYNX.md\");\n const globalInstructions: string[] = [];\n try {\n if (existsSync(globalLynxMd) && statSync(globalLynxMd).isFile()) {\n globalInstructions.push(`# LYNX.md — 全局用户指令\\n${readFileSync(globalLynxMd, \"utf-8\")}`);\n }\n } catch {\n // 文件不存在或不可读 — 跳过\n }\n\n const userRulesDir = join(homedir(), \".lynx\", \"rules\");\n const projectRulesDir = join(workspace, \".claude\", \"rules\");\n const rules = [\n ...globalInstructions,\n ...loadTextFilesFromDir(userRulesDir),\n ...loadTextFilesFromDir(projectRulesDir),\n ];\n\n // 6. Agent engine — config is a mutable object so model can be swapped at runtime\n const agentConfig: AgentConfig = {\n provider: config.provider,\n model: config.model,\n systemPrompt: getBaseSystemPrompt(),\n maxTokens: 8192,\n maxCompactionFailures: 3,\n budget: { maxTokens: 200_000, maxUsd: 10, maxTurns: 100 },\n };\n\n const engine = createQueryEngine({\n config: agentConfig,\n provider: config.provider,\n toolHandlers,\n allTools: allToolsWithMcp,\n skills,\n memoryFacts,\n rules,\n checkPermission: async (toolName, safety, description) => {\n // Circuit breaker: auto‑deny if user denied 3 consecutive high‑safety tools\n if (denialTracker.isTripped()) return false;\n\n const safetyLevel = safety as \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n const approved = await permissionBridge.requestPermission(toolName, safetyLevel, description);\n\n // Only track user-facing decisions (Safe/WorkspaceSafe auto‑approve without dialog)\n if (safetyLevel === \"RequiresApproval\" || safetyLevel === \"Dangerous\") {\n if (approved) {\n denialTracker.recordApproval();\n } else {\n denialTracker.recordDenial();\n }\n }\n return approved;\n },\n });\n\n // 7. Task manager — background task lifecycle for the task tool\n const taskManager = createTaskManager();\n injectTaskManager(taskManager);\n\n const ctx: AppContext = {\n db,\n sessionMgr,\n toolRegistry,\n pluginRegistry,\n ruleEngine,\n engine,\n provider: config.provider,\n agentConfig,\n permissionBridge,\n skillRegistry,\n skills,\n mcpManager,\n predictiveLoader,\n channelRegistry,\n memoryManager,\n memoryFacts,\n rules,\n\n destroy(): void {\n taskManager.destroy();\n channelRegistry.destroyAll();\n mcpManager.destroy();\n engine.destroy();\n sessionMgr.destroy();\n db.close();\n },\n };\n\n return ctx;\n}\n","/**\n * Two‑phase startup — split blocking init from background work.\n *\n * Phase 1 (≤ 1.5s): version check → config load → SQLite open → migrate.\n * Phase 2 (background): plugin discovery → MCP connect → memory load.\n *\n * The phases are split so the TUI can render as soon as Phase 1\n * finishes, giving the user immediate feedback.\n */\n\nimport type { Database } from \"@24klynx/core\";\nimport { openDatabase, migrate, resolvePaths, type LynxPaths } from \"@24klynx/core\";\nimport type { PluginRuntimeContext } from \"@24klynx/plugins\";\nimport type { AppContext } from \"./bootstrap.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface Phase1Result {\n paths: LynxPaths;\n db: Database;\n /** Wall‑clock ms for Phase 1. */\n elapsedMs: number;\n}\n\nexport interface StartupResult extends Phase1Result {\n /** Wall‑clock ms for Phase 2. */\n phase2ElapsedMs: number;\n}\n\n/** Result of a single Phase 2 background task. */\ninterface Phase2TaskResult {\n name: string;\n ok: boolean;\n error?: string;\n elapsedMs: number;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Run Phase 1 — blocking startup that must finish before\n * the user sees anything useful.\n *\n * Returns a database handle the rest of the app can use.\n */\nexport function runPhase1(): Phase1Result {\n const started = Date.now();\n\n const paths = resolvePaths();\n const db = openDatabase({ dbPath: paths.stateDb });\n migrate(db);\n\n const elapsedMs = Date.now() - started;\n return { paths, db, elapsedMs };\n}\n\n/**\n * Run Phase 2 — background work that executes after the TUI is rendered.\n *\n * Tasks run sequentially with a setImmediate gap between each so the\n * event loop stays responsive. Each task is independently try‑catched\n * so a single failure doesn't block the rest.\n *\n * Returns the elapsed time in ms.\n */\nexport async function runPhase2(result: Phase1Result): Promise<Phase2TaskResult[]> {\n return runPhase2Tasks(null, result.paths);\n}\n\n/**\n * Run Phase 2 with the full AppContext (preferred path).\n *\n * When called from the TUI launcher, the AppContext provides access\n * to all initialized services (plugin registry, MCP manager, etc.).\n *\n * Tasks:\n * 1. scanExtensions — discover plugins in the extensions directory\n * 2. preloadSkills — index skills for autocomplete\n * 3. connectMcpServers — establish MCP connections\n * 4. loadMemory — load memory files into the agent context\n */\nexport async function runPhase2WithContext(\n ctx: AppContext,\n paths: LynxPaths,\n): Promise<Phase2TaskResult[]> {\n return runPhase2Tasks(ctx, paths);\n}\n\n// ── Internals ──────────────────────────────────────────\n\ninterface Phase2Logger {\n info(msg: string): void;\n warn(msg: string): void;\n}\n\nfunction createSilentLogger(): Phase2Logger {\n return {\n info: () => {},\n warn: (msg) => process.stderr.write(`[phase2] ${msg}\\n`),\n };\n}\n\nasync function runPhase2Tasks(\n ctx: AppContext | null,\n paths: LynxPaths,\n): Promise<Phase2TaskResult[]> {\n const logger = createSilentLogger();\n const results: Phase2TaskResult[] = [];\n\n const tasks: Array<() => Promise<void>> = [\n async () => {\n await scanExtensions(ctx, paths, logger);\n },\n async () => {\n await preloadSkills(ctx, paths, logger);\n },\n async () => {\n await connectMcpServers(ctx, logger);\n },\n async () => {\n await loadMemory(ctx, paths, logger);\n },\n async () => {\n await initChannels(ctx, logger);\n },\n ];\n\n for (const task of tasks) {\n // Yield to the event loop so the TUI stays responsive\n await new Promise((resolve) => setImmediate(resolve));\n\n const started = Date.now();\n const name = task.name || \"unknown\";\n try {\n await task();\n results.push({ name, ok: true, elapsedMs: Date.now() - started });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(`${name} failed: ${message}`);\n results.push({ name, ok: false, error: message, elapsedMs: Date.now() - started });\n }\n }\n\n if (results.some((r) => !r.ok)) {\n const failures = results\n .filter((r) => !r.ok)\n .map((r) => r.name)\n .join(\", \");\n logger.warn(`Phase 2 completed with failures: ${failures}`);\n } else {\n logger.info(`Phase 2 completed: ${results.length} tasks OK`);\n }\n\n return results;\n}\n\n// ── Task implementations ──────────────────────────────\n\n/**\n * Scan the extensions directory for plugins, register manifests,\n * and execute plugin code via the PluginLoader.\n *\n * Looks in:\n * 1. <lynx-home>/extensions/ (user extensions)\n * 2. <project>/.lynx/extensions/ (project extensions)\n * 3. <lynx-install>/extensions/ (built‑in extensions)\n */\nasync function scanExtensions(\n ctx: AppContext | null,\n paths: LynxPaths,\n logger: Phase2Logger,\n): Promise<void> {\n if (!ctx) return; // No plugin registry available\n\n const { readdirSync, statSync } = await import(\"node:fs\");\n const { join } = await import(\"node:path\");\n\n const scanDirs = [join(paths.home, \"extensions\"), join(process.cwd(), \".lynx\", \"extensions\")];\n\n for (const dir of scanDirs) {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n continue; // Directory doesn't exist — skip\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n try {\n if (!statSync(fullPath).isDirectory()) continue;\n } catch {\n continue;\n }\n\n // Each extension directory should have a manifest.json\n const manifestPath = join(fullPath, \"manifest.json\");\n try {\n statSync(manifestPath);\n } catch {\n continue; // No manifest — not a valid extension\n }\n\n try {\n const discovered = ctx.pluginRegistry.manifestRegistry.scan([fullPath]);\n // Actually load and execute each discovered plugin\n for (const manifestEntry of discovered) {\n const pluginCtx: PluginRuntimeContext = {\n pluginId: manifestEntry.manifest.name,\n logger: {\n info: (msg) => logger.info(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n warn: (msg) => logger.warn(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n error: (msg) => logger.warn(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n },\n config: {},\n storage: createPluginStorage(),\n };\n ctx.pluginRegistry.loader.load(manifestEntry, pluginCtx).catch((err) => {\n logger.warn(`Plugin \"${manifestEntry.manifest.name}\" failed to load: ${String(err)}`);\n });\n }\n } catch {\n // Duplicate registration or invalid manifest — skip\n }\n }\n }\n}\n\n/**\n * Create a simple in‑memory KV store for plugin storage.\n *\n * In Phase 5 this will be backed by the SQLite database\n * for persistence across sessions.\n */\nfunction createPluginStorage(): PluginRuntimeContext[\"storage\"] {\n const store = new Map<string, unknown>();\n return {\n get<T>(key: string): T | undefined {\n return store.get(key) as T | undefined;\n },\n set<T>(key: string, value: T): void {\n store.set(key, value);\n },\n delete(key: string): void {\n store.delete(key);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\n/**\n * Preload skill definitions for autocomplete and quick access.\n *\n * Skills are already loaded during bootstrap (Phase 1). This Phase 2 task\n * enqueues plugins for predictive warming through the PredictiveLoader,\n * reducing first‑use latency for plugins that are likely to be needed.\n */\nasync function preloadSkills(\n ctx: AppContext | null,\n _paths: LynxPaths,\n _logger: Phase2Logger,\n): Promise<void> {\n if (!ctx) return;\n\n const loader = ctx.predictiveLoader;\n\n // Warm plugins based on input‑prefix heuristics\n loader.onInputPrefix(\"/\"); // skills via slash commands\n loader.onInputPrefix(\"@\"); // model/provider picker\n\n // Process the warmup queue (non‑blocking, yields between imports)\n await loader.processQueue();\n}\n\n/**\n * Connect to configured MCP servers.\n *\n * MCP connections are established during bootstrap (Phase 1) via\n * McpManager.connect() for all configured servers. Connection\n * failures are tracked via McpConnection.status.\n *\n * This Phase 2 task exists as a hook point for re‑connection or\n * health check logic in future phases.\n */\nasync function connectMcpServers(_ctx: AppContext | null, _logger: Phase2Logger): Promise<void> {\n // MCP connections are established in bootstrap.\n // Future: health‑check + auto‑reconnect here.\n}\n\n/**\n * Load memory files into the agent's context.\n *\n * Memory is loaded during bootstrap (Phase 1) from:\n * 1. ~/.lynx/memory/\n * 2. <workspace>/.claude/memory/\n *\n * Content flows through AppContext.memoryFacts → EngineDeps →\n * LoopDeps → assembleSystemPrompt (\"Memory\" section).\n *\n * This Phase 2 task exists as a hook point for runtime memory\n * reload (e.g. after file changes).\n */\nasync function loadMemory(\n _ctx: AppContext | null,\n paths: LynxPaths,\n _logger: Phase2Logger,\n): Promise<void> {\n // Memory is already loaded in bootstrap.\n // Future: watch for file changes and reload memory into ctx.\n void paths; // keep parameter for future file‑watching use\n}\n\n/**\n * Initialize registered channel adapters (飞书 etc.).\n *\n * Channels are registered during bootstrap (Phase 1) but their\n * init() (auth, WS connect) is deferred to Phase 2 so the TUI\n * is already visible before network I/O starts.\n */\nasync function initChannels(ctx: AppContext | null, logger: Phase2Logger): Promise<void> {\n if (!ctx) return;\n const channelIds = ctx.channelRegistry.list();\n if (channelIds.length === 0) {\n logger.info(\"No channel adapters registered — skipping channel init\");\n return;\n }\n logger.info(`Initializing channels: ${channelIds.join(\", \")}`);\n await ctx.channelRegistry.initAll();\n logger.info(`Channels initialized: ${channelIds.join(\", \")}`);\n}\n","/**\n * Terminal mode management — alt buffer, mouse tracking, DEC sync.\n *\n * Provides functions to enter/exit the alternate screen buffer,\n * enable/disable mouse tracking, and wrap output with DEC\n * Synchronized Update markers for flicker‑free rendering.\n *\n * All escape sequences are no‑ops on unsupported terminals\n * (graceful degradation).\n */\n\n// ── Escape sequences ──────────────────────────────────\n\n/** Enter alternate screen buffer. */\nconst ALT_ENTER = \"\\x1b[?1049h\";\n/** Exit alternate screen buffer. */\nconst ALT_EXIT = \"\\x1b[?1049l\";\n\n/** Begin DEC Synchronized Update — subsequent output is buffered. */\nconst BSU = \"\\x1b[?2026h\";\n/** End DEC Synchronized Update — flush buffered output atomically. */\nconst ESU = \"\\x1b[?2026l\";\n\n// ── Public API ───────────────────────────────────────\n\n/** Enter fullscreen mode: alt buffer only (no mouse tracking). */\nexport function enterFullscreen(): void {\n process.stdout.write(ALT_ENTER);\n}\n\n/** Exit fullscreen mode: restore main buffer. */\nexport function exitFullscreen(): void {\n process.stdout.write(ALT_EXIT);\n}\n\n/**\n * Wrap a synchronous callback with DEC Synchronized Update markers.\n *\n * All output written to stdout during the callback is buffered by the\n * terminal and rendered atomically, eliminating flicker during re‑renders.\n *\n * Does NOT nest — calling beginSync inside an active sync region\n * is a no‑op (tracked via module‑level flag).\n */\nlet syncActive = false;\n\nexport function beginSync(): void {\n if (syncActive) return;\n syncActive = true;\n process.stdout.write(BSU);\n}\n\nexport function endSync(): void {\n if (!syncActive) return;\n syncActive = false;\n process.stdout.write(ESU);\n}\n\n/**\n * Execute a callback within a DEC synchronized update region.\n * Exceptions propagate; sync is always ended.\n */\nexport function withSync<T>(fn: () => T): T {\n beginSync();\n try {\n return fn();\n } finally {\n endSync();\n }\n}\n","/**\n * Process lifecycle — signal handling, terminal loss detection,\n * graceful shutdown, and force‑exit timer.\n *\n * Integrates with the 3‑layer abort system:\n * SIGINT 1 → abort LLM request\n * SIGINT 2 → abort running tool\n * SIGINT 3 → process.exit(1)\n *\n * Provides:\n * - SIGINT / SIGTERM / SIGHUP handlers\n * - Terminal loss detection (stdin/stdout close)\n * - Force exit timer (2s after shutdown starts)\n * - Graceful cleanup: flush draft, close DB\n */\n\nimport type { AppContext } from \"./bootstrap.js\";\nimport { exitFullscreen } from \"./terminal-mode.js\";\nimport { loadConfig } from \"./commands/config.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface LifecycleConfig {\n /** AppContext for graceful cleanup. */\n ctx: AppContext;\n /** Callback to abort the current LLM request. */\n onAbortLl: () => void;\n /** Callback to abort the current tool execution. */\n onAbortTool: () => void;\n /** Current abort layer counter (0‑3). Gets incremented by SIGINT. */\n getAbortLayer: () => number;\n /** Increment and return the next abort layer. */\n incrementAbortLayer: () => number;\n /** Reset abort layer counter. */\n resetAbortLayer: () => void;\n /** Whether the TUI is currently streaming (in an active turn). */\n isStreaming: () => boolean;\n}\n\n// ── State ────────────────────────────────────────────\n\nlet _cleanupHandler: (() => void) | null = null;\nlet hardExitTimer: NodeJS.Timeout | null = null;\nlet installed = false;\n\n/** Maximum time (ms) allowed for graceful shutdown before force exit. */\nconst FORCE_EXIT_MS = 2_000;\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Install process lifecycle handlers.\n *\n * Only call once. Subsequent calls are no‑ops.\n * Handles SIGINT (3‑layer abort), SIGTERM (graceful shutdown),\n * SIGHUP (graceful shutdown), and terminal loss.\n */\nexport function installProcessLifecycle(config: LifecycleConfig): void {\n if (installed) return;\n installed = true;\n\n const { ctx, onAbortLl, onAbortTool, incrementAbortLayer, isStreaming } = config;\n\n // ── SIGINT — 3‑layer abort ──────────────────────\n const onSigint = () => {\n const layer = incrementAbortLayer();\n\n if (layer >= 3) {\n // Hard exit\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n }\n\n if (!isStreaming()) {\n // No active stream → exit immediately\n exitFullscreen();\n ctx.destroy();\n process.exit(0);\n }\n\n if (layer === 1) {\n onAbortLl();\n } else if (layer === 2) {\n onAbortTool();\n }\n };\n\n process.on(\"SIGINT\", onSigint);\n\n // ── SIGTERM — graceful shutdown ────────────────\n const gracefulShutdown = () => {\n startForceExitTimer();\n\n try {\n exitFullscreen();\n } catch {\n // Terminal may already be gone\n }\n\n try {\n ctx.destroy();\n } catch {\n // Best effort\n }\n\n process.exit(0);\n };\n\n process.on(\"SIGTERM\", gracefulShutdown);\n\n // ── SIGHUP — reload configuration ───────────────\n // Unix convention: SIGHUP = reload, not kill.\n // Falls back to graceful shutdown if reload fails.\n process.on(\"SIGHUP\", () => {\n try {\n const newCfg = loadConfig();\n // Apply model change if present\n if (typeof newCfg.model === \"string\" && newCfg.model !== ctx.agentConfig.model) {\n ctx.agentConfig.model = newCfg.model;\n process.stderr.write(`[lynx] SIGHUP:模型已切换 → ${newCfg.model}\\n`);\n }\n // Theme changes are handled by the TUI on next render\n if (typeof newCfg.theme === \"string\") {\n process.stderr.write(`[lynx] SIGHUP:主题已切换 → ${newCfg.theme}\\n`);\n }\n process.stderr.write(\"[lynx] SIGHUP:配置已重新加载\\n\");\n } catch (reloadErr) {\n process.stderr.write(\n `[lynx] SIGHUP 重新加载失败:${reloadErr instanceof Error ? reloadErr.message : String(reloadErr)} — 回退到关闭流程\\n`,\n );\n gracefulShutdown();\n }\n });\n\n // ── Terminal loss detection ─────────────────────\n const onTerminalLost = () => {\n // stdin/stdout closed — terminal was killed\n startForceExitTimer();\n try {\n ctx.destroy();\n } catch {\n // Best effort\n }\n process.exit(0);\n };\n\n process.stdin.on(\"end\", onTerminalLost);\n process.stdin.on(\"close\", onTerminalLost);\n process.stdout.on(\"close\", onTerminalLost);\n\n // ── Unhandled rejection — log and exit ──────────\n process.on(\"unhandledRejection\", (reason) => {\n process.stderr.write(\n `[lynx] 未处理的 Promise 拒绝:${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}\\n`,\n );\n startForceExitTimer();\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n });\n\n // ── Uncaught exception — log and exit ───────────\n process.on(\"uncaughtException\", (err) => {\n process.stderr.write(`[lynx] 未捕获的异常:${err.stack ?? err.message}\\n`);\n startForceExitTimer();\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n });\n}\n\n/**\n * Register a cleanup handler that runs during graceful shutdown.\n * Replaces any previously registered handler.\n */\nexport function onCleanup(handler: () => void): void {\n _cleanupHandler = handler;\n}\n\n/**\n * Remove all process lifecycle handlers (for testing).\n */\nexport function uninstallProcessLifecycle(): void {\n installed = false;\n process.removeAllListeners(\"SIGINT\");\n process.removeAllListeners(\"SIGTERM\");\n process.removeAllListeners(\"SIGHUP\");\n if (hardExitTimer) {\n clearTimeout(hardExitTimer);\n hardExitTimer = null;\n }\n _cleanupHandler = null;\n}\n\n// ── Internals ──────────────────────────────────────────\n\nfunction startForceExitTimer(): void {\n if (hardExitTimer) return;\n hardExitTimer = setTimeout(() => {\n process.stderr.write(\"[lynx] 超时后强制退出\\n\");\n process.exit(1);\n }, FORCE_EXIT_MS);\n hardExitTimer.unref(); // Don't block the event loop\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@24klynx/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI entry point — bootstrap, command catalog, crestodian, doctor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"commander": "^13.1.0",
|
|
19
19
|
"ink": "^6.8.0",
|
|
20
20
|
"react": "^19.2.0",
|
|
21
|
-
"@24klynx/agent": "0.1.
|
|
22
|
-
"@24klynx/
|
|
23
|
-
"@24klynx/
|
|
24
|
-
"@24klynx/
|
|
25
|
-
"@24klynx/
|
|
26
|
-
"@24klynx/
|
|
27
|
-
"@24klynx/
|
|
28
|
-
"@24klynx/
|
|
29
|
-
"@24klynx/
|
|
21
|
+
"@24klynx/agent": "0.1.2",
|
|
22
|
+
"@24klynx/channels": "0.1.2",
|
|
23
|
+
"@24klynx/permissions": "0.1.2",
|
|
24
|
+
"@24klynx/llm": "0.1.2",
|
|
25
|
+
"@24klynx/core": "0.1.2",
|
|
26
|
+
"@24klynx/session": "0.1.2",
|
|
27
|
+
"@24klynx/plugins": "0.1.2",
|
|
28
|
+
"@24klynx/tui": "0.1.2",
|
|
29
|
+
"@24klynx/tools": "0.1.2"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
package/dist/config-D-xVXTXi.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-Des0z-k9.mjs","names":[],"sources":["../src/commands/config.ts"],"sourcesContent":["/**\n * Config command — 统一配置文件管理与 Provider 工厂。\n *\n * 读取 ~/.lynx/config.json,提供类型安全的结构化配置。\n * 同时输出 resolveConfigEnv(env 注入)和 resolveProvider(按模型名自动选型)。\n */\n\nimport { readFileSync, writeFileSync, existsSync, renameSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { resolvePaths } from \"@lynx/core\";\nimport type { LlmProvider } from \"@lynx/llm\";\nimport { createDeepSeekProvider, createAnthropicProvider, createOpenAiProvider } from \"@lynx/llm\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Lynx 完整配置结构。对应 ~/.lynx/config.json。 */\nexport interface LynxConfig {\n /** 模型标识符(如 deepseek-v4-pro、claude-sonnet-4-6)。 */\n model: string;\n /** UI 主题(dark / light)。 */\n theme: string;\n /** 环境变量注入 — key 不存在于 process.env 时自动设置。 */\n env: Record<string, string>;\n /** 权限白/黑名单。格式 \"ToolName(args)\"。 */\n permissions: {\n allow: string[];\n deny: string[];\n };\n}\n\nexport interface ConfigCommandOptions {\n show?: boolean;\n set?: string;\n value?: string;\n path?: boolean;\n}\n\n// ── Defaults ─────────────────────────────────────────\n\nconst DEFAULT_CONFIG: LynxConfig = {\n model: \"deepseek-v4-pro\",\n theme: \"dark\",\n env: {},\n permissions: { allow: [], deny: [] },\n};\n\n// ── Helpers ──────────────────────────────────────────\n\nfunction configPath(): string {\n const paths = resolvePaths();\n return paths.configFile;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 从磁盘加载配置,缺失字段填默认值。\n * 文件损坏时自动备份为 .bak 并返回默认配置。\n */\nexport function loadConfig(): LynxConfig {\n const path = configPath();\n if (!existsSync(path))\n return { ...DEFAULT_CONFIG, env: {}, permissions: { allow: [], deny: [] } };\n try {\n const raw = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<LynxConfig>;\n return {\n model: raw.model ?? DEFAULT_CONFIG.model,\n theme: raw.theme ?? DEFAULT_CONFIG.theme,\n env: raw.env ?? {},\n permissions: {\n allow: raw.permissions?.allow ?? [],\n deny: raw.permissions?.deny ?? [],\n },\n };\n } catch {\n const backupPath = path + \".bak\";\n try {\n renameSync(path, backupPath);\n } catch {\n // 备份失败不阻塞\n }\n process.stderr.write(\n `[lynx] 警告:配置文件已损坏,已备份到 ${backupPath}。正在使用默认配置。\\n`,\n );\n return { ...DEFAULT_CONFIG, env: {}, permissions: { allow: [], deny: [] } };\n }\n}\n\n/**\n * 将 config.env 中的键注入 process.env(仅补未设的 key,不覆盖已有环境变量)。\n *\n * 优先级:已存在的 process.env > config.json env\n */\nexport function resolveConfigEnv(config: LynxConfig): void {\n for (const [key, value] of Object.entries(config.env)) {\n if (process.env[key] === undefined && value !== undefined && value !== \"\") {\n process.env[key] = value;\n }\n }\n}\n\n/**\n * 按模型名自动匹配 LLM Provider。\n *\n * 匹配规则:\n * - 含 `claude` → Anthropic(需 ANTHROPIC_AUTH_TOKEN 或 ANTHROPIC_API_KEY)\n * - 含 `gpt` 或 `o1`/`o3` → OpenAI(需 OPENAI_API_KEY)\n * - 其他 → DeepSeek(需 DEEPSEEK_API_KEY)\n *\n * 环境变量优先于 config.json env(resolveConfigEnv 已处理)。\n *\n * @throws 找不到对应 API key 时抛出中文错误。\n */\nexport function resolveProvider(config: LynxConfig): LlmProvider {\n const model = process.env.LYNX_MODEL ?? config.model ?? \"deepseek-v4-pro\";\n\n if (model.includes(\"claude\")) {\n const token = process.env.ANTHROPIC_AUTH_TOKEN ?? process.env.ANTHROPIC_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 Anthropic API 密钥。请在 config.json 的 env.ANTHROPIC_AUTH_TOKEN 中设置,\" +\n \"或设置环境变量 ANTHROPIC_API_KEY。\",\n );\n }\n return createAnthropicProvider({\n apiKey: token,\n baseUrl: process.env.ANTHROPIC_BASE_URL,\n });\n }\n\n if (model.includes(\"gpt\") || model.includes(\"o1\") || model.includes(\"o3\")) {\n const token = process.env.OPENAI_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 OpenAI API 密钥。请在 config.json 的 env.OPENAI_API_KEY 中设置,\" +\n \"或设置环境变量 OPENAI_API_KEY。\",\n );\n }\n return createOpenAiProvider({ apiKey: token });\n }\n\n // 默认:DeepSeek\n const token = process.env.DEEPSEEK_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 DeepSeek API 密钥。请在 config.json 的 env.DEEPSEEK_API_KEY 中设置,\" +\n \"或设置环境变量 DEEPSEEK_API_KEY。\",\n );\n }\n return createDeepSeekProvider({\n apiKey: token,\n baseUrl: process.env.DEEPSEEK_BASE_URL,\n });\n}\n\n/** 原子写入配置到磁盘(temp file + rename)。 */\nexport function saveConfig(config: LynxConfig): void {\n const path = configPath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n const tmpPath = path + \".tmp\";\n // 排序键以保证 diff 友好\n const ordered = {\n model: config.model,\n theme: config.theme,\n env: config.env,\n permissions: config.permissions,\n };\n writeFileSync(tmpPath, JSON.stringify(ordered, null, 2) + \"\\n\", \"utf-8\");\n renameSync(tmpPath, path);\n}\n\n// ── CLI handler ──────────────────────────────────────\n\n/** 处理 `lynx config` 命令。 */\nexport async function handleConfigCommand(opts: ConfigCommandOptions): Promise<void> {\n if (opts.path) {\n process.stdout.write(`${configPath()}\\n`);\n return;\n }\n\n if (opts.set) {\n const config = loadConfig();\n // 简单顶层 key 直写;嵌套 key 暂不支持\n const key = opts.set as keyof LynxConfig;\n if (key === \"model\" || key === \"theme\") {\n config[key] = opts.value ?? \"\";\n } else if (key === \"env\" || key === \"permissions\") {\n process.stderr.write(`[lynx] 警告:${opts.set} 是嵌套对象,请直接编辑 config.json。\\n`);\n return;\n }\n saveConfig(config);\n process.stdout.write(`${opts.set} = ${opts.value ?? \"\"}\\n`);\n return;\n }\n\n // Default: --show\n const config = loadConfig();\n const keys = Object.keys(config);\n if (keys.length === 0) {\n process.stdout.write(\"No configuration values set.\\n\");\n } else {\n for (const [key, val] of Object.entries(config)) {\n process.stdout.write(`${key} = ${JSON.stringify(val)}\\n`);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"env-CeeZcoDI.mjs","names":[],"sources":["../src/commands/env.ts"],"sourcesContent":["/**\n * /env — 显示 Lynx 环境变量和运行环境信息。\n *\n * 读取所有 LYNX_* 环境变量、Node.js 版本、操作系统平台\n * 和 Lynx 主目录,以表格形式输出。\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { resolvePaths } from \"@lynx/core\";\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /env 命令。\n *\n * 收集并格式化输出所有 Lynx 相关的环境信息。\n * 不返回 instruction(这是 local 类型命令,直接在 CLI 进程中执行)。\n */\nexport function handleEnvCommand(): { output: string } {\n const lines: string[] = [];\n\n // ── LYNX_* 环境变量 ──────────────────────────────\n const lynxVars = Object.keys(process.env)\n .filter((key) => key.startsWith(\"LYNX_\"))\n .sort();\n\n lines.push(\"【Lynx 环境变量】\");\n lines.push(\"\");\n\n if (lynxVars.length === 0) {\n lines.push(\" (未设置任何 LYNX_* 环境变量)\");\n } else {\n for (const key of lynxVars) {\n const value = process.env[key];\n const display = value && value.length > 0 ? value : \"(空)\";\n lines.push(` ${key.padEnd(30)} = ${display}`);\n }\n }\n\n // ── 已定义但未设置的常见变量 ──────────────────────\n const knownVars = [\"LYNX_HOME\", \"LYNX_CONFIG_PATH\", \"LYNX_LOG_LEVEL\", \"LYNX_MODEL\"];\n const missingVars = knownVars.filter((v) => !lynxVars.includes(v));\n if (missingVars.length > 0) {\n lines.push(\"\");\n lines.push(\"【未设置的常见变量】\");\n lines.push(\"\");\n for (const key of missingVars) {\n lines.push(` ${key.padEnd(30)} = (未设置,使用默认值)`);\n }\n }\n\n // ── 运行环境 ──────────────────────────────────────\n const paths = resolvePaths();\n\n lines.push(\"\");\n lines.push(\"【运行环境】\");\n lines.push(\"\");\n lines.push(` ${\"Node.js 版本\".padEnd(30)} = ${process.version}`);\n lines.push(` ${\"操作系统\".padEnd(30)} = ${process.platform} (${process.arch})`);\n lines.push(` ${\"Lynx 主目录\".padEnd(30)} = ${join(homedir(), \".lynx\")}`);\n lines.push(` ${\"配置文件路径\".padEnd(30)} = ${paths.configFile}`);\n lines.push(` ${\"数据库路径\".padEnd(30)} = ${paths.stateDb}`);\n lines.push(` ${\"工作目录\".padEnd(30)} = ${process.cwd()}`);\n\n return { output: lines.join(\"\\n\") };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,SAAgB,mBAAuC;CACrD,MAAM,QAAkB,CAAC;CAGzB,MAAM,WAAW,OAAO,KAAK,QAAQ,GAAG,CAAC,CACtC,QAAQ,QAAQ,IAAI,WAAW,OAAO,CAAC,CAAC,CACxC,KAAK;CAER,MAAM,KAAK,aAAa;CACxB,MAAM,KAAK,EAAE;CAEb,IAAI,SAAS,WAAW,GACtB,MAAM,KAAK,uBAAuB;MAElC,KAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,QAAQ,QAAQ,IAAI;EAC1B,MAAM,UAAU,SAAS,MAAM,SAAS,IAAI,QAAQ;EACpD,MAAM,KAAK,KAAK,IAAI,OAAO,EAAE,EAAE,KAAK,SAAS;CAC/C;CAKF,MAAM,cAAc;EADD;EAAa;EAAoB;EAAkB;CAC1C,CAAC,CAAC,QAAQ,MAAM,CAAC,SAAS,SAAS,CAAC,CAAC;CACjE,IAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,YAAY;EACvB,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,OAAO,aAChB,MAAM,KAAK,KAAK,IAAI,OAAO,EAAE,EAAE,eAAe;CAElD;CAGA,MAAM,QAAQ,aAAa;CAE3B,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,QAAQ;CACnB,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,KAAK,aAAa,OAAO,EAAE,EAAE,KAAK,QAAQ,SAAS;CAC9D,MAAM,KAAK,KAAK,OAAO,OAAO,EAAE,EAAE,KAAK,QAAQ,SAAS,IAAI,QAAQ,KAAK,EAAE;CAC3E,MAAM,KAAK,KAAK,WAAW,OAAO,EAAE,EAAE,KAAK,KAAK,QAAQ,GAAG,OAAO,GAAG;CACrE,MAAM,KAAK,KAAK,SAAS,OAAO,EAAE,EAAE,KAAK,MAAM,YAAY;CAC3D,MAAM,KAAK,KAAK,QAAQ,OAAO,EAAE,EAAE,KAAK,MAAM,SAAS;CACvD,MAAM,KAAK,KAAK,OAAO,OAAO,EAAE,EAAE,KAAK,QAAQ,IAAI,GAAG;CAEtD,OAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,EAAE;AACpC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"headless-launcher-I8NWyD6k.mjs","names":[],"sources":["../src/headless-launcher.ts"],"sourcesContent":["/**\n * headless-launcher — non-interactive mode for Lynx.\n *\n * Used when `lynx start --headless` is invoked. Starts the agent engine\n * and optionally a messaging channel (飞书 etc.) for remote interaction.\n * No TUI is rendered — all I/O goes through the channel adapter.\n *\n * Responsibility:\n * 1. Bootstrap the application (same as tui-launcher)\n * 2. Register channel adapters (飞书 etc.)\n * 3. Process incoming messages through the engine\n * 4. Stream responses back through the channel\n */\n\nimport { resolvePaths, asMessageId } from \"@lynx/core\";\nimport { loadConfig, resolveConfigEnv, resolveProvider } from \"./commands/config.js\";\nimport { bootstrap } from \"./bootstrap.js\";\nimport { runPhase2WithContext } from \"./startup.js\";\nimport { installProcessLifecycle } from \"./process-lifecycle.js\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { homedir } from \"node:os\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface HeadlessConfig {\n /** Model to use (overrides LYNX_MODEL env var). */\n model?: string;\n /** Enable verbose logging. */\n verbose?: boolean;\n /** 飞书 channel configuration. */\n feishu?: { appId: string; appSecret: string };\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Start Lynx in headless (non-interactive) mode.\n *\n * If a 飞书 configuration is provided, the agent listens for\n * incoming messages from the 飞书 channel and responds via\n * the same channel. Otherwise, runs a simple REPL on stdin/stdout.\n */\nexport async function startHeadless(config: HeadlessConfig = {}): Promise<void> {\n const paths = resolvePaths();\n\n // 加载配置 → 注入 env → 按模型名自动选 Provider\n const appConfig = loadConfig();\n resolveConfigEnv(appConfig);\n const provider = resolveProvider(appConfig);\n\n // Resolve built‑in skills directory relative to the lynx-agent package\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const builtinSkillsDir = join(__dirname, \"..\", \"..\", \"lynx-agent\", \"skills\");\n\n const ctx = bootstrap({\n homeDir: paths.home,\n provider,\n model: config.model ?? process.env.LYNX_MODEL ?? appConfig.model,\n skillsDir: builtinSkillsDir,\n workspace: process.cwd(),\n feishu: config.feishu,\n });\n\n const sessions = ctx.sessionMgr.list();\n const activeSession =\n sessions.length > 0 ? sessions[0]! : ctx.sessionMgr.create(\"default\", process.cwd());\n\n // ── Abort layer state ────────────────────────────\n let abortLayer = 0;\n let isStreaming = false;\n\n const getAbortLayer = () => abortLayer;\n const incrementAbortLayer = () => {\n abortLayer++;\n return abortLayer;\n };\n const resetAbortLayer = () => {\n abortLayer = 0;\n };\n\n // ── Process lifecycle ───────────────────────────\n installProcessLifecycle({\n ctx,\n onAbortLl: () => ctx.engine.abort(),\n onAbortTool: () => ctx.engine.abort(),\n getAbortLayer,\n incrementAbortLayer,\n resetAbortLayer,\n isStreaming: () => isStreaming,\n });\n\n // ── Phase 2: background tasks ───────────────────\n runPhase2WithContext(ctx, paths).catch(() => {\n // Errors are already logged by the task runner\n });\n\n // ── Message handler (shared across all channels) ─\n async function processMessage(msg: { text: string }): Promise<string> {\n isStreaming = true;\n resetAbortLayer();\n\n const userMsg = {\n id: asMessageId(crypto.randomUUID()),\n role: \"user\" as const,\n content: [{ type: \"text\" as const, text: msg.text }],\n timestamp: Date.now(),\n turnIndex: activeSession.messages.length,\n };\n\n // Add user message to the session\n activeSession.messages.push(userMsg);\n\n const controller = new AbortController();\n let responseText = \"\";\n\n try {\n for await (const event of ctx.engine.submit(activeSession, userMsg, controller.signal)) {\n switch (event.type) {\n case \"text_delta\":\n responseText += event.text;\n break;\n case \"reasoning_delta\":\n // Reasoning tokens — skip in headless mode (not sent to channel)\n break;\n case \"tool_use_start\":\n if (config.verbose) {\n process.stderr.write(`[headless] Tool call: ${event.name}\\n`);\n }\n break;\n case \"tool_result\":\n if (config.verbose) {\n const truncated =\n event.content.length > 200 ? event.content.slice(0, 200) + \"...\" : event.content;\n process.stderr.write(`[headless] Tool result: ${truncated}\\n`);\n }\n break;\n case \"error\":\n responseText += `\\nError: ${event.message}`;\n break;\n case \"done\":\n break;\n }\n }\n } catch (err) {\n responseText += `\\nError: ${err instanceof Error ? err.message : String(err)}`;\n } finally {\n isStreaming = false;\n }\n\n // Add assistant response to the session\n if (responseText) {\n const reply = {\n id: asMessageId(crypto.randomUUID()),\n role: \"assistant\" as const,\n content: [{ type: \"text\" as const, text: responseText }],\n timestamp: Date.now(),\n turnIndex: activeSession.messages.length,\n };\n activeSession.messages.push(reply);\n }\n\n return responseText;\n }\n\n // ── Channel or stdin mode ───────────────────────\n const channelIds = ctx.channelRegistry.list();\n\n if (channelIds.length > 0) {\n // Channel mode — use the first registered channel for I/O.\n // The registry wires each adapter's onMessage() to the centralized handler.\n // Since Message doesn't carry a channelId, we get the adapter directly\n // and bypass the centralized handler for request/response pairing.\n process.stderr.write(`[headless] Listening on channels: ${channelIds.join(\", \")}\\n`);\n\n const primaryChannelId = channelIds[0]!;\n const adapter = ctx.channelRegistry.get(primaryChannelId);\n\n if (!adapter) {\n process.stderr.write(`[headless] Channel \"${primaryChannelId}\" not found\\n`);\n process.exitCode = 1;\n return;\n }\n\n // Listen for incoming messages on the primary channel\n adapter.onMessage((msg) => {\n const textContent = msg.content\n .filter((c) => c.type === \"text\")\n .map((c) => (c as { text: string }).text)\n .join(\"\\n\");\n\n if (!textContent) return;\n\n processMessage({ text: textContent })\n .then(async (replyText) => {\n if (replyText) {\n const replyMsg = {\n id: asMessageId(crypto.randomUUID()),\n role: \"assistant\" as const,\n content: [{ type: \"text\" as const, text: replyText }],\n timestamp: Date.now(),\n turnIndex: activeSession.messages.length,\n };\n await adapter.sendMessage(replyMsg);\n }\n })\n .catch((err) => {\n process.stderr.write(\n `[headless] Channel message processing failed: ` +\n `${err instanceof Error ? err.message : String(err)}\\n`,\n );\n });\n });\n\n // Keep process alive — channels run indefinitely\n await new Promise<void>(() => {\n // Never resolves — process runs until SIGTERM/SIGINT\n });\n } else {\n // Stdin mode — read one message and respond\n process.stderr.write(\"[headless] No channels configured — stdin mode\\n\");\n process.stderr.write(\"[headless] Enter your message (Ctrl+D to end):\\n\");\n\n const chunks: string[] = [];\n process.stdin.setEncoding(\"utf-8\");\n\n for await (const chunk of process.stdin) {\n chunks.push(chunk as string);\n }\n\n const text = chunks.join(\"\").trim();\n if (!text) {\n process.stderr.write(\"[headless] Empty input — exiting\\n\");\n process.exitCode = 1;\n return;\n }\n\n const replyText = await processMessage({ text });\n process.stdout.write(replyText + \"\\n\");\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"process-lifecycle-Dg6n2QS-.mjs","names":[],"sources":["../src/mcp-loader.ts","../src/bootstrap.ts","../src/startup.ts","../src/terminal-mode.ts","../src/process-lifecycle.ts"],"sourcesContent":["/**\n * MCP configuration loader — reads MCP server configurations\n * from Lynx settings files (3‑layer merge: global → project → local).\n *\n * Settings layers (later overrides earlier for same‑named servers):\n * 1. ~/.lynx/settings.json — global\n * 2. .lynx/settings.json — project (committed)\n * 3. .lynx/settings.local.json — project local (gitignored)\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolvePaths } from \"@lynx/core\";\nimport type { McpServerConfig } from \"@lynx/agent\";\nimport { join } from \"node:path\";\n\n// ── Types ────────────────────────────────────────────\n\ninterface SettingsFile {\n mcpServers?: McpServerConfig[];\n enableAllProjectMcpServers?: boolean;\n enabledMcpjsonServers?: string[];\n disabledMcpjsonServers?: string[];\n}\n\n/** Where we found the config for a server. */\nexport type McpConfigSource = \"global\" | \"project\" | \"project-local\";\n\nexport interface McpServerEntry {\n config: McpServerConfig;\n source: McpConfigSource;\n}\n\nexport interface McpLoadResult {\n /** Merged server configs (project overrides global for same name). */\n servers: McpServerConfig[];\n /** Per‑server metadata, keyed by server name. */\n entries: Map<string, McpServerEntry>;\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/**\n * Try to read and parse a JSON file. Returns undefined if the file\n * does not exist or is malformed.\n */\nfunction tryReadJson<T>(filePath: string): T | undefined {\n if (!existsSync(filePath)) return undefined;\n try {\n return JSON.parse(readFileSync(filePath, \"utf-8\")) as T;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Find the project root by walking up from cwd looking for `.lynx/`.\n * Returns undefined if no project root is found.\n */\nfunction findProjectRoot(startDir: string): string | undefined {\n let dir = startDir;\n for (let i = 0; i < 50; i++) {\n if (existsSync(join(dir, \".lynx\"))) return dir;\n const parent = join(dir, \"..\");\n if (parent === dir) return undefined;\n dir = parent;\n }\n return undefined;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Load MCP server configurations from all settings layers.\n *\n * Merge order: global → project → project‑local.\n * Same‑named servers in later layers override earlier ones entirely.\n */\nexport function loadMcpConfigs(workspaceDir?: string): McpLoadResult {\n const paths = resolvePaths();\n\n // 1. Global settings\n const globalSettings = tryReadJson<SettingsFile>(paths.settingsFile);\n\n // 2. Project settings\n const projectRoot = workspaceDir ? findProjectRoot(workspaceDir) : findProjectRoot(process.cwd());\n const projectSettings = projectRoot\n ? tryReadJson<SettingsFile>(join(projectRoot, \".lynx\", \"settings.json\"))\n : undefined;\n\n // 3. Project‑local settings\n const localSettings = projectRoot\n ? tryReadJson<SettingsFile>(join(projectRoot, \".lynx\", \"settings.local.json\"))\n : undefined;\n\n // Merge: global → project → local (later wins)\n const merged = new Map<string, McpServerEntry>();\n\n function addServers(servers: McpServerConfig[] | undefined, source: McpConfigSource): void {\n if (!servers) return;\n for (const server of servers) {\n merged.set(server.name, { config: server, source });\n }\n }\n\n addServers(globalSettings?.mcpServers, \"global\");\n addServers(projectSettings?.mcpServers, \"project\");\n addServers(localSettings?.mcpServers, \"project-local\");\n\n return {\n servers: Array.from(merged.values()).map((e) => e.config),\n entries: merged,\n };\n}\n\n/**\n * Convenience: load and validate MCP configs for bootstrap.\n *\n * Returns an array of valid McpServerConfig objects, filtering out\n * entries that have neither a `command` nor a `url`.\n */\nexport function loadAndValidateMcpConfigs(workspaceDir?: string): McpServerConfig[] {\n const result = loadMcpConfigs(workspaceDir);\n return result.servers.filter((s) => {\n const isValid = !!(s.command || s.url);\n if (!isValid) {\n // Logged at trace level — most callers don't need to see this\n }\n return isValid;\n });\n}\n","/**\n * Bootstrap — the single assembly point for all Lynx dependencies.\n *\n * Every service is created via factory functions and wired together\n * here. No DI container — dependencies are passed explicitly.\n *\n * Order: infrastructure → core services → engine → TUI bridge.\n */\n\nimport type { Database } from \"@lynx/core\";\nimport { openDatabase, migrate } from \"@lynx/core\";\nimport type { SessionManager } from \"@lynx/session\";\nimport { createSessionManager } from \"@lynx/session\";\nimport type { ToolRegistry, ToolHandler, ToolDescriptor } from \"@lynx/tools\";\nimport {\n createToolRegistry,\n registerBuiltinTools,\n createMemoryWriteHandler,\n memoryWriteDescriptor,\n injectTaskManager,\n mcpAuthDescriptor,\n createMcpAuthHandler,\n sendMessageDescriptor,\n createSendMessageHandler,\n} from \"@lynx/tools\";\nimport type { LlmProvider } from \"@lynx/llm\";\nimport type { ManifestRegistry, PluginLoader, HookRegistry } from \"@lynx/plugins\";\nimport {\n createManifestRegistry,\n createPluginLoader,\n createHookRegistry,\n PredictiveLoader,\n} from \"@lynx/plugins\";\nimport type {\n QueryEngine,\n AgentConfig,\n SkillRegistry,\n SkillDefinition,\n McpManager,\n McpServerConfig,\n} from \"@lynx/agent\";\nimport {\n createQueryEngine,\n createSkillRegistry,\n createSkillToolHandler,\n createMcpManager,\n createMemoryManager,\n createTaskManager,\n} from \"@lynx/agent\";\nimport { getBaseSystemPrompt } from \"@lynx/agent\";\nimport type { MemoryManager, TaskManager } from \"@lynx/agent\";\nimport type { RuleEngine } from \"@lynx/permissions\";\nimport {\n createRuleEngine,\n createRulesLoader,\n createDenialTracker,\n loadFromDisk,\n} from \"@lynx/permissions\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { randomUUID } from \"node:crypto\";\nimport { loadAndValidateMcpConfigs } from \"./mcp-loader.js\";\nimport { createChannelRegistry, createFeishuAdapter } from \"@lynx/channels\";\nimport type { ChannelRegistry, FeishuConfig } from \"@lynx/channels\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Bridge between the agent engine's permission checks and the TUI's permission dialog. */\nexport interface PermissionBridge {\n /**\n * Called by the agent engine before executing a tool.\n * Returns true if the tool is allowed, false if denied.\n * May await user interaction via the TUI dialog.\n */\n requestPermission(\n toolName: string,\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\",\n description: string,\n ): Promise<boolean>;\n /**\n * Called by the TUI's onPermissionReply callback.\n * Resolves the pending permission Promise.\n */\n handleReply(requestId: string, approved: boolean): void;\n /**\n * Called by the TUI after mount to register the dialog trigger.\n * The handler pushes a permission view onto the TUI stack.\n */\n setTuiHandler(\n handler:\n | ((req: {\n requestId: string;\n toolName: string;\n description: string;\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n }) => void)\n | null,\n ): void;\n}\n\nexport interface AppContext {\n db: Database;\n sessionMgr: SessionManager;\n toolRegistry: ToolRegistry;\n pluginRegistry: {\n manifestRegistry: ManifestRegistry;\n loader: PluginLoader;\n hooks: HookRegistry;\n };\n ruleEngine: RuleEngine;\n engine: QueryEngine;\n provider: LlmProvider;\n /** Mutable agent config — model can be changed at runtime without recreating the engine. */\n agentConfig: AgentConfig;\n /** Permission bridge for tool approval flow. */\n permissionBridge: PermissionBridge;\n /** Skill registry for progressive disclosure (built‑in + user skills). */\n skillRegistry: SkillRegistry;\n /** Current skill definitions for prompt injection. */\n skills: SkillDefinition[];\n /** MCP connection manager for external tool servers. */\n mcpManager: McpManager;\n /** Predictive loader — pre‑warms plugin modules during idle time. */\n predictiveLoader: PredictiveLoader;\n /** Channel registry — manages messaging channel adapters (飞书 etc.). */\n channelRegistry: ChannelRegistry;\n /** Memory manager for persistent cross-session recall. */\n memoryManager: MemoryManager;\n /** Persistent memory facts loaded from disk. */\n memoryFacts: string[];\n /** Textual rules loaded from disk for prompt injection. */\n rules: string[];\n /** Destroy all resources (close DB, stop timers, etc.). */\n destroy(): void;\n}\n\nexport interface BootstrapConfig {\n homeDir: string;\n provider: LlmProvider;\n model: string;\n /** Workspace directory (defaults to cwd). Used for loading project-level memory/rules/skills. */\n workspace?: string;\n /** Path to built‑in skills directory (SKILL.md files). */\n skillsDir?: string;\n /** MCP server configurations to connect on startup. */\n mcpServers?: McpServerConfig[];\n /** 飞书 channel configuration (optional — channel not created if omitted). */\n feishu?: FeishuConfig;\n}\n\n// ── Permission bridge factory ─────────────────────────\n\n/** Timeout for auto‑denying a permission request when the user doesn't respond. */\nconst PERMISSION_TIMEOUT_MS = 60_000;\n\ninterface PendingPermission {\n resolve: (approved: boolean) => void;\n timer: NodeJS.Timeout;\n}\n\n/**\n * Create a permission bridge that mediates between the agent engine\n * and the TUI's permission dialog.\n *\n * The bridge uses a Promise Map pattern:\n * 1. Agent calls `requestPermission()` → Promise created + stored\n * 2. TUI handler fires → shows permission dialog\n * 3. User responds → `handleReply()` resolves the Promise\n * 4. Agent resumes (or skips the tool)\n *\n * Safety levels \"Safe\" and \"WorkspaceSafe\" are auto‑allowed without\n * showing the dialog.\n */\nfunction createPermissionBridge(): PermissionBridge {\n const pending = new Map<string, PendingPermission>();\n let tuiHandler:\n | ((req: {\n requestId: string;\n toolName: string;\n description: string;\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n }) => void)\n | null = null;\n\n const bridge: PermissionBridge = {\n async requestPermission(toolName, safety, description): Promise<boolean> {\n // Auto‑allow safe operations\n if (safety === \"Safe\" || safety === \"WorkspaceSafe\") return true;\n\n return new Promise<boolean>((resolve) => {\n const requestId = randomUUID();\n const timer = setTimeout(() => {\n pending.delete(requestId);\n resolve(false);\n }, PERMISSION_TIMEOUT_MS);\n\n pending.set(requestId, { resolve, timer });\n\n // Signal the TUI to show the permission dialog\n if (tuiHandler) {\n tuiHandler({ requestId, toolName, description, safety });\n } else {\n // No TUI attached (headless mode) → auto‑deny\n clearTimeout(timer);\n pending.delete(requestId);\n resolve(false);\n }\n });\n },\n\n handleReply(requestId, approved): void {\n const entry = pending.get(requestId);\n if (entry) {\n clearTimeout(entry.timer);\n pending.delete(requestId);\n entry.resolve(approved);\n }\n },\n\n setTuiHandler(handler): void {\n tuiHandler = handler;\n },\n };\n\n return bridge;\n}\n\n// ── Helpers ────────────────────────────────────────────\n\n/**\n * Load all .md files from a directory as an array of file contents.\n * Silently returns [] if the directory doesn't exist or is unreadable.\n */\nfunction loadTextFilesFromDir(dir: string): string[] {\n const facts: string[] = [];\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return facts;\n }\n for (const entry of entries) {\n if (!entry.endsWith(\".md\")) continue;\n const fullPath = join(dir, entry);\n try {\n if (!statSync(fullPath).isFile()) continue;\n facts.push(readFileSync(fullPath, \"utf-8\"));\n } catch {\n // Skip unreadable files\n }\n }\n return facts;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Bootstrap the entire Lynx application.\n *\n * This is the ONLY place where cross‑package wiring happens.\n * Every other module only talks to its immediate dependencies\n * through explicit factory‑injected interfaces.\n */\nexport function bootstrap(config: BootstrapConfig): AppContext {\n const dbPath = join(config.homeDir, \"state.db\");\n const workspace = config.workspace ?? process.cwd();\n\n // 1. Infrastructure\n const db = openDatabase({ dbPath });\n migrate(db);\n\n // 2. Core services\n const sessionMgr = createSessionManager(db);\n const toolRegistry = createToolRegistry();\n const ruleEngine = createRuleEngine();\n const rulesLoader = createRulesLoader(ruleEngine);\n\n // Register built‑in tools (files, agent, mode, interact)\n registerBuiltinTools(toolRegistry);\n\n // 3. Skills — progressive disclosure system with multi‑directory support\n // Default built‑in skills directory relative to lynx‑agent package\n const skillsDir =\n config.skillsDir ?? join(config.homeDir, \"..\", \"packages\", \"lynx-agent\", \"skills\");\n const skillRegistry = createSkillRegistry(skillsDir);\n\n // Also scan user and project skill directories\n const userSkillsDir = join(homedir(), \".lynx\", \"skills\");\n skillRegistry.addDirectory(userSkillsDir);\n const projectSkillsDir = join(workspace, \".claude\", \"skills\");\n skillRegistry.addDirectory(projectSkillsDir);\n\n const skills = skillRegistry.list();\n\n // Register the Skill tool so the model can request full skill bodies\n const skillToolDescriptor: ToolDescriptor = {\n name: \"Skill\",\n description:\n \"加载技能完整指令。支持 load(加载技能内容)、list(列出所有技能)、\" +\n \"search(搜索技能)、reload(重新加载技能目录)四种操作。\",\n inputSchema: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"load\", \"list\", \"search\", \"reload\"],\n description:\n \"操作类型:load 加载技能内容、list 列出所有技能、search 搜索技能、reload 重新加载\",\n },\n skill: { type: \"string\", description: \"要加载的技能名称(load 操作时使用)\" },\n args: { type: \"string\", description: \"传递给技能的可选参数\" },\n query: { type: \"string\", description: \"搜索关键词(search 操作时使用)\" },\n },\n },\n kind: \"ReadOnly\",\n safety: \"Safe\",\n availability: { type: \"always\" },\n executor: \"Skill\",\n owner: \"core\",\n };\n toolRegistry.register(skillToolDescriptor, createSkillToolHandler(skillRegistry));\n\n // 3a. MCP — connection manager for external tool servers\n const mcpManager = createMcpManager();\n\n // Load MCP servers from settings files (global → project → local)\n const settingsMcpServers = loadAndValidateMcpConfigs();\n\n // Merge programmatic config with settings-loaded configs\n // Programmatic config takes precedence for same‑named servers\n const programmaticServers = config.mcpServers ?? [];\n const settingsOnly = settingsMcpServers.filter(\n (s) => !programmaticServers.some((p) => p.name === s.name),\n );\n const allMcpServers = [...programmaticServers, ...settingsOnly];\n\n // Connect all configured MCP servers (non‑blocking — tools appear when connected)\n for (const serverConfig of allMcpServers) {\n mcpManager.connect(serverConfig).catch(() => {\n // Connection failures are tracked via McpConnection.status;\n // the agent can call reconnect() to retry.\n });\n }\n\n // 3b. Channel registry — messaging channels (飞书 etc.)\n const channelRegistry = createChannelRegistry();\n\n // Register 飞书 adapter if configured\n if (config.feishu?.appId && config.feishu?.appSecret) {\n const feishuAdapter = createFeishuAdapter(config.feishu);\n channelRegistry.register(feishuAdapter);\n }\n\n // Rebuild toolHandlers map to include the Skill tool\n // Keys are tool NAMES (not executors) because loop.ts looks up by call.name\n const toolHandlers = new Map<string, ToolHandler>();\n const allTools = toolRegistry.listAll();\n for (const desc of allTools) {\n try {\n const handler = toolRegistry.resolveExecutor(desc);\n toolHandlers.set(desc.name, handler);\n } catch {\n // Handler not registered — skip (will be reported as \"unknown tool\" at runtime)\n }\n }\n\n // Register MCP dispatcher — each MCP tool gets a handler that forwards calls\n // to the McpManager, which routes to the correct server process.\n const mcpDispatcher: ToolHandler = {\n async handle(invocation, _signal) {\n const result = await mcpManager.callTool(invocation.toolName, invocation.payload);\n return {\n content: result.content,\n success: !result.isError,\n };\n },\n };\n // Register MCP tools — warn on name conflicts instead of silently overwriting\n for (const mcpTool of mcpManager.getAllTools()) {\n if (toolHandlers.has(mcpTool.name)) {\n process.stderr.write(`[lynx] 警告:MCP 工具 \"${mcpTool.name}\" 与现有工具冲突 — 已跳过\\n`);\n continue;\n }\n toolHandlers.set(mcpTool.name, mcpDispatcher);\n }\n\n // Merge MCP tools into the allTools catalog so they are visible to the model.\n // Filter out tools that were skipped due to name conflicts.\n const mcpToolNames = new Set(mcpManager.getAllTools().map((t) => t.name));\n const allToolsWithMcp = [\n ...allTools,\n ...mcpManager.getAllTools().filter((t) => !allTools.some((b) => b.name === t.name)),\n ];\n\n // 4. Plugin system\n const manifestRegistry = createManifestRegistry();\n const hookRegistry = createHookRegistry();\n const pluginLoader = createPluginLoader();\n const pluginRegistry = { manifestRegistry, loader: pluginLoader, hooks: hookRegistry };\n\n // 4a. Predictive loader — pre‑warms plugin modules during Phase 2 idle time\n const predictiveLoader = new PredictiveLoader(async (pluginId: string) => {\n // Dynamic import of plugin modules; pluginId is an npm package name\n // or a file path prefixed with \"file:\" for local extensions\n return import(pluginId);\n });\n\n // 4b. Load permission rules from disk (settings.json + permissions.json)\n loadFromDisk(rulesLoader, workspace);\n\n // 4c. Denial tracker — circuit breaker for consecutive denials\n const denialTracker = createDenialTracker();\n\n // 5. Permission bridge — shared between engine and TUI\n const permissionBridge = createPermissionBridge();\n\n // 5a. Memory management\n const userMemoryDir = join(homedir(), \".lynx\", \"memory\");\n const projectMemoryDir = join(workspace, \".claude\", \"memory\");\n const memoryManager = createMemoryManager(userMemoryDir);\n const memoryFacts = [\n ...(memoryManager\n .list()\n .map((e) => memoryManager.get(e.name)?.content)\n .filter(Boolean) as string[]),\n ...loadTextFilesFromDir(projectMemoryDir),\n ];\n\n // Register memory_write tool — needs MemoryManager injection\n const memoryWriteHandler = createMemoryWriteHandler(memoryManager);\n toolRegistry.register(memoryWriteDescriptor, memoryWriteHandler);\n toolHandlers.set(memoryWriteDescriptor.name, memoryWriteHandler);\n allTools.push(memoryWriteDescriptor);\n\n // Register McpAuthTool — needs McpManager injection for reconnection\n const mcpAuthOps = {\n reconnect: async (serverName: string) => {\n const conn = await mcpManager.reconnect(serverName);\n return { status: conn.status };\n },\n };\n const mcpAuthHandler = createMcpAuthHandler(mcpAuthOps);\n toolRegistry.register(mcpAuthDescriptor, mcpAuthHandler);\n toolHandlers.set(mcpAuthDescriptor.name, mcpAuthHandler);\n allTools.push(mcpAuthDescriptor);\n\n // Register SendMessageTool — needs channel registry injection\n const sendMessageSender = {\n async send(channel: string, content: string, recipients?: string[]) {\n const adapter = channelRegistry.get(channel);\n if (!adapter) throw new Error(`未注册的消息通道:${channel}`);\n const message = {\n id: randomUUID() as unknown as import(\"@lynx/core\").MessageId,\n role: \"assistant\" as const,\n content: [{ type: \"text\" as const, text: content }],\n timestamp: Date.now(),\n turnIndex: 0,\n };\n const messageId = await adapter.sendMessage(message);\n return { messageId };\n },\n };\n const sendMessageHandler = createSendMessageHandler(sendMessageSender);\n toolRegistry.register(sendMessageDescriptor, sendMessageHandler);\n toolHandlers.set(sendMessageDescriptor.name, sendMessageHandler);\n allTools.push(sendMessageDescriptor);\n\n // 全局用户指令(~/.lynx/LYNX.md)— 优先级最高,在所有项目中生效\n const globalLynxMd = join(homedir(), \".lynx\", \"LYNX.md\");\n const globalInstructions: string[] = [];\n try {\n if (existsSync(globalLynxMd) && statSync(globalLynxMd).isFile()) {\n globalInstructions.push(`# LYNX.md — 全局用户指令\\n${readFileSync(globalLynxMd, \"utf-8\")}`);\n }\n } catch {\n // 文件不存在或不可读 — 跳过\n }\n\n const userRulesDir = join(homedir(), \".lynx\", \"rules\");\n const projectRulesDir = join(workspace, \".claude\", \"rules\");\n const rules = [\n ...globalInstructions,\n ...loadTextFilesFromDir(userRulesDir),\n ...loadTextFilesFromDir(projectRulesDir),\n ];\n\n // 6. Agent engine — config is a mutable object so model can be swapped at runtime\n const agentConfig: AgentConfig = {\n provider: config.provider,\n model: config.model,\n systemPrompt: getBaseSystemPrompt(),\n maxTokens: 8192,\n maxCompactionFailures: 3,\n budget: { maxTokens: 200_000, maxUsd: 10, maxTurns: 100 },\n };\n\n const engine = createQueryEngine({\n config: agentConfig,\n provider: config.provider,\n toolHandlers,\n allTools: allToolsWithMcp,\n skills,\n memoryFacts,\n rules,\n checkPermission: async (toolName, safety, description) => {\n // Circuit breaker: auto‑deny if user denied 3 consecutive high‑safety tools\n if (denialTracker.isTripped()) return false;\n\n const safetyLevel = safety as \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n const approved = await permissionBridge.requestPermission(toolName, safetyLevel, description);\n\n // Only track user-facing decisions (Safe/WorkspaceSafe auto‑approve without dialog)\n if (safetyLevel === \"RequiresApproval\" || safetyLevel === \"Dangerous\") {\n if (approved) {\n denialTracker.recordApproval();\n } else {\n denialTracker.recordDenial();\n }\n }\n return approved;\n },\n });\n\n // 7. Task manager — background task lifecycle for the task tool\n const taskManager = createTaskManager();\n injectTaskManager(taskManager);\n\n const ctx: AppContext = {\n db,\n sessionMgr,\n toolRegistry,\n pluginRegistry,\n ruleEngine,\n engine,\n provider: config.provider,\n agentConfig,\n permissionBridge,\n skillRegistry,\n skills,\n mcpManager,\n predictiveLoader,\n channelRegistry,\n memoryManager,\n memoryFacts,\n rules,\n\n destroy(): void {\n taskManager.destroy();\n channelRegistry.destroyAll();\n mcpManager.destroy();\n engine.destroy();\n sessionMgr.destroy();\n db.close();\n },\n };\n\n return ctx;\n}\n","/**\n * Two‑phase startup — split blocking init from background work.\n *\n * Phase 1 (≤ 1.5s): version check → config load → SQLite open → migrate.\n * Phase 2 (background): plugin discovery → MCP connect → memory load.\n *\n * The phases are split so the TUI can render as soon as Phase 1\n * finishes, giving the user immediate feedback.\n */\n\nimport type { Database } from \"@lynx/core\";\nimport { openDatabase, migrate, resolvePaths, type LynxPaths } from \"@lynx/core\";\nimport type { PluginRuntimeContext } from \"@lynx/plugins\";\nimport type { AppContext } from \"./bootstrap.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface Phase1Result {\n paths: LynxPaths;\n db: Database;\n /** Wall‑clock ms for Phase 1. */\n elapsedMs: number;\n}\n\nexport interface StartupResult extends Phase1Result {\n /** Wall‑clock ms for Phase 2. */\n phase2ElapsedMs: number;\n}\n\n/** Result of a single Phase 2 background task. */\ninterface Phase2TaskResult {\n name: string;\n ok: boolean;\n error?: string;\n elapsedMs: number;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Run Phase 1 — blocking startup that must finish before\n * the user sees anything useful.\n *\n * Returns a database handle the rest of the app can use.\n */\nexport function runPhase1(): Phase1Result {\n const started = Date.now();\n\n const paths = resolvePaths();\n const db = openDatabase({ dbPath: paths.stateDb });\n migrate(db);\n\n const elapsedMs = Date.now() - started;\n return { paths, db, elapsedMs };\n}\n\n/**\n * Run Phase 2 — background work that executes after the TUI is rendered.\n *\n * Tasks run sequentially with a setImmediate gap between each so the\n * event loop stays responsive. Each task is independently try‑catched\n * so a single failure doesn't block the rest.\n *\n * Returns the elapsed time in ms.\n */\nexport async function runPhase2(result: Phase1Result): Promise<Phase2TaskResult[]> {\n return runPhase2Tasks(null, result.paths);\n}\n\n/**\n * Run Phase 2 with the full AppContext (preferred path).\n *\n * When called from the TUI launcher, the AppContext provides access\n * to all initialized services (plugin registry, MCP manager, etc.).\n *\n * Tasks:\n * 1. scanExtensions — discover plugins in the extensions directory\n * 2. preloadSkills — index skills for autocomplete\n * 3. connectMcpServers — establish MCP connections\n * 4. loadMemory — load memory files into the agent context\n */\nexport async function runPhase2WithContext(\n ctx: AppContext,\n paths: LynxPaths,\n): Promise<Phase2TaskResult[]> {\n return runPhase2Tasks(ctx, paths);\n}\n\n// ── Internals ──────────────────────────────────────────\n\ninterface Phase2Logger {\n info(msg: string): void;\n warn(msg: string): void;\n}\n\nfunction createSilentLogger(): Phase2Logger {\n return {\n info: () => {},\n warn: (msg) => process.stderr.write(`[phase2] ${msg}\\n`),\n };\n}\n\nasync function runPhase2Tasks(\n ctx: AppContext | null,\n paths: LynxPaths,\n): Promise<Phase2TaskResult[]> {\n const logger = createSilentLogger();\n const results: Phase2TaskResult[] = [];\n\n const tasks: Array<() => Promise<void>> = [\n async () => {\n await scanExtensions(ctx, paths, logger);\n },\n async () => {\n await preloadSkills(ctx, paths, logger);\n },\n async () => {\n await connectMcpServers(ctx, logger);\n },\n async () => {\n await loadMemory(ctx, paths, logger);\n },\n async () => {\n await initChannels(ctx, logger);\n },\n ];\n\n for (const task of tasks) {\n // Yield to the event loop so the TUI stays responsive\n await new Promise((resolve) => setImmediate(resolve));\n\n const started = Date.now();\n const name = task.name || \"unknown\";\n try {\n await task();\n results.push({ name, ok: true, elapsedMs: Date.now() - started });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(`${name} failed: ${message}`);\n results.push({ name, ok: false, error: message, elapsedMs: Date.now() - started });\n }\n }\n\n if (results.some((r) => !r.ok)) {\n const failures = results\n .filter((r) => !r.ok)\n .map((r) => r.name)\n .join(\", \");\n logger.warn(`Phase 2 completed with failures: ${failures}`);\n } else {\n logger.info(`Phase 2 completed: ${results.length} tasks OK`);\n }\n\n return results;\n}\n\n// ── Task implementations ──────────────────────────────\n\n/**\n * Scan the extensions directory for plugins, register manifests,\n * and execute plugin code via the PluginLoader.\n *\n * Looks in:\n * 1. <lynx-home>/extensions/ (user extensions)\n * 2. <project>/.lynx/extensions/ (project extensions)\n * 3. <lynx-install>/extensions/ (built‑in extensions)\n */\nasync function scanExtensions(\n ctx: AppContext | null,\n paths: LynxPaths,\n logger: Phase2Logger,\n): Promise<void> {\n if (!ctx) return; // No plugin registry available\n\n const { readdirSync, statSync } = await import(\"node:fs\");\n const { join } = await import(\"node:path\");\n\n const scanDirs = [join(paths.home, \"extensions\"), join(process.cwd(), \".lynx\", \"extensions\")];\n\n for (const dir of scanDirs) {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n continue; // Directory doesn't exist — skip\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n try {\n if (!statSync(fullPath).isDirectory()) continue;\n } catch {\n continue;\n }\n\n // Each extension directory should have a manifest.json\n const manifestPath = join(fullPath, \"manifest.json\");\n try {\n statSync(manifestPath);\n } catch {\n continue; // No manifest — not a valid extension\n }\n\n try {\n const discovered = ctx.pluginRegistry.manifestRegistry.scan([fullPath]);\n // Actually load and execute each discovered plugin\n for (const manifestEntry of discovered) {\n const pluginCtx: PluginRuntimeContext = {\n pluginId: manifestEntry.manifest.name,\n logger: {\n info: (msg) => logger.info(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n warn: (msg) => logger.warn(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n error: (msg) => logger.warn(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n },\n config: {},\n storage: createPluginStorage(),\n };\n ctx.pluginRegistry.loader.load(manifestEntry, pluginCtx).catch((err) => {\n logger.warn(`Plugin \"${manifestEntry.manifest.name}\" failed to load: ${String(err)}`);\n });\n }\n } catch {\n // Duplicate registration or invalid manifest — skip\n }\n }\n }\n}\n\n/**\n * Create a simple in‑memory KV store for plugin storage.\n *\n * In Phase 5 this will be backed by the SQLite database\n * for persistence across sessions.\n */\nfunction createPluginStorage(): PluginRuntimeContext[\"storage\"] {\n const store = new Map<string, unknown>();\n return {\n get<T>(key: string): T | undefined {\n return store.get(key) as T | undefined;\n },\n set<T>(key: string, value: T): void {\n store.set(key, value);\n },\n delete(key: string): void {\n store.delete(key);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\n/**\n * Preload skill definitions for autocomplete and quick access.\n *\n * Skills are already loaded during bootstrap (Phase 1). This Phase 2 task\n * enqueues plugins for predictive warming through the PredictiveLoader,\n * reducing first‑use latency for plugins that are likely to be needed.\n */\nasync function preloadSkills(\n ctx: AppContext | null,\n _paths: LynxPaths,\n _logger: Phase2Logger,\n): Promise<void> {\n if (!ctx) return;\n\n const loader = ctx.predictiveLoader;\n\n // Warm plugins based on input‑prefix heuristics\n loader.onInputPrefix(\"/\"); // skills via slash commands\n loader.onInputPrefix(\"@\"); // model/provider picker\n\n // Process the warmup queue (non‑blocking, yields between imports)\n await loader.processQueue();\n}\n\n/**\n * Connect to configured MCP servers.\n *\n * MCP connections are established during bootstrap (Phase 1) via\n * McpManager.connect() for all configured servers. Connection\n * failures are tracked via McpConnection.status.\n *\n * This Phase 2 task exists as a hook point for re‑connection or\n * health check logic in future phases.\n */\nasync function connectMcpServers(_ctx: AppContext | null, _logger: Phase2Logger): Promise<void> {\n // MCP connections are established in bootstrap.\n // Future: health‑check + auto‑reconnect here.\n}\n\n/**\n * Load memory files into the agent's context.\n *\n * Memory is loaded during bootstrap (Phase 1) from:\n * 1. ~/.lynx/memory/\n * 2. <workspace>/.claude/memory/\n *\n * Content flows through AppContext.memoryFacts → EngineDeps →\n * LoopDeps → assembleSystemPrompt (\"Memory\" section).\n *\n * This Phase 2 task exists as a hook point for runtime memory\n * reload (e.g. after file changes).\n */\nasync function loadMemory(\n _ctx: AppContext | null,\n paths: LynxPaths,\n _logger: Phase2Logger,\n): Promise<void> {\n // Memory is already loaded in bootstrap.\n // Future: watch for file changes and reload memory into ctx.\n void paths; // keep parameter for future file‑watching use\n}\n\n/**\n * Initialize registered channel adapters (飞书 etc.).\n *\n * Channels are registered during bootstrap (Phase 1) but their\n * init() (auth, WS connect) is deferred to Phase 2 so the TUI\n * is already visible before network I/O starts.\n */\nasync function initChannels(ctx: AppContext | null, logger: Phase2Logger): Promise<void> {\n if (!ctx) return;\n const channelIds = ctx.channelRegistry.list();\n if (channelIds.length === 0) {\n logger.info(\"No channel adapters registered — skipping channel init\");\n return;\n }\n logger.info(`Initializing channels: ${channelIds.join(\", \")}`);\n await ctx.channelRegistry.initAll();\n logger.info(`Channels initialized: ${channelIds.join(\", \")}`);\n}\n","/**\n * Terminal mode management — alt buffer, mouse tracking, DEC sync.\n *\n * Provides functions to enter/exit the alternate screen buffer,\n * enable/disable mouse tracking, and wrap output with DEC\n * Synchronized Update markers for flicker‑free rendering.\n *\n * All escape sequences are no‑ops on unsupported terminals\n * (graceful degradation).\n */\n\n// ── Escape sequences ──────────────────────────────────\n\n/** Enter alternate screen buffer. */\nconst ALT_ENTER = \"\\x1b[?1049h\";\n/** Exit alternate screen buffer. */\nconst ALT_EXIT = \"\\x1b[?1049l\";\n\n/** Begin DEC Synchronized Update — subsequent output is buffered. */\nconst BSU = \"\\x1b[?2026h\";\n/** End DEC Synchronized Update — flush buffered output atomically. */\nconst ESU = \"\\x1b[?2026l\";\n\n// ── Public API ───────────────────────────────────────\n\n/** Enter fullscreen mode: alt buffer only (no mouse tracking). */\nexport function enterFullscreen(): void {\n process.stdout.write(ALT_ENTER);\n}\n\n/** Exit fullscreen mode: restore main buffer. */\nexport function exitFullscreen(): void {\n process.stdout.write(ALT_EXIT);\n}\n\n/**\n * Wrap a synchronous callback with DEC Synchronized Update markers.\n *\n * All output written to stdout during the callback is buffered by the\n * terminal and rendered atomically, eliminating flicker during re‑renders.\n *\n * Does NOT nest — calling beginSync inside an active sync region\n * is a no‑op (tracked via module‑level flag).\n */\nlet syncActive = false;\n\nexport function beginSync(): void {\n if (syncActive) return;\n syncActive = true;\n process.stdout.write(BSU);\n}\n\nexport function endSync(): void {\n if (!syncActive) return;\n syncActive = false;\n process.stdout.write(ESU);\n}\n\n/**\n * Execute a callback within a DEC synchronized update region.\n * Exceptions propagate; sync is always ended.\n */\nexport function withSync<T>(fn: () => T): T {\n beginSync();\n try {\n return fn();\n } finally {\n endSync();\n }\n}\n","/**\n * Process lifecycle — signal handling, terminal loss detection,\n * graceful shutdown, and force‑exit timer.\n *\n * Integrates with the 3‑layer abort system:\n * SIGINT 1 → abort LLM request\n * SIGINT 2 → abort running tool\n * SIGINT 3 → process.exit(1)\n *\n * Provides:\n * - SIGINT / SIGTERM / SIGHUP handlers\n * - Terminal loss detection (stdin/stdout close)\n * - Force exit timer (2s after shutdown starts)\n * - Graceful cleanup: flush draft, close DB\n */\n\nimport type { AppContext } from \"./bootstrap.js\";\nimport { exitFullscreen } from \"./terminal-mode.js\";\nimport { loadConfig } from \"./commands/config.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface LifecycleConfig {\n /** AppContext for graceful cleanup. */\n ctx: AppContext;\n /** Callback to abort the current LLM request. */\n onAbortLl: () => void;\n /** Callback to abort the current tool execution. */\n onAbortTool: () => void;\n /** Current abort layer counter (0‑3). Gets incremented by SIGINT. */\n getAbortLayer: () => number;\n /** Increment and return the next abort layer. */\n incrementAbortLayer: () => number;\n /** Reset abort layer counter. */\n resetAbortLayer: () => void;\n /** Whether the TUI is currently streaming (in an active turn). */\n isStreaming: () => boolean;\n}\n\n// ── State ────────────────────────────────────────────\n\nlet _cleanupHandler: (() => void) | null = null;\nlet hardExitTimer: NodeJS.Timeout | null = null;\nlet installed = false;\n\n/** Maximum time (ms) allowed for graceful shutdown before force exit. */\nconst FORCE_EXIT_MS = 2_000;\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Install process lifecycle handlers.\n *\n * Only call once. Subsequent calls are no‑ops.\n * Handles SIGINT (3‑layer abort), SIGTERM (graceful shutdown),\n * SIGHUP (graceful shutdown), and terminal loss.\n */\nexport function installProcessLifecycle(config: LifecycleConfig): void {\n if (installed) return;\n installed = true;\n\n const { ctx, onAbortLl, onAbortTool, incrementAbortLayer, isStreaming } = config;\n\n // ── SIGINT — 3‑layer abort ──────────────────────\n const onSigint = () => {\n const layer = incrementAbortLayer();\n\n if (layer >= 3) {\n // Hard exit\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n }\n\n if (!isStreaming()) {\n // No active stream → exit immediately\n exitFullscreen();\n ctx.destroy();\n process.exit(0);\n }\n\n if (layer === 1) {\n onAbortLl();\n } else if (layer === 2) {\n onAbortTool();\n }\n };\n\n process.on(\"SIGINT\", onSigint);\n\n // ── SIGTERM — graceful shutdown ────────────────\n const gracefulShutdown = () => {\n startForceExitTimer();\n\n try {\n exitFullscreen();\n } catch {\n // Terminal may already be gone\n }\n\n try {\n ctx.destroy();\n } catch {\n // Best effort\n }\n\n process.exit(0);\n };\n\n process.on(\"SIGTERM\", gracefulShutdown);\n\n // ── SIGHUP — reload configuration ───────────────\n // Unix convention: SIGHUP = reload, not kill.\n // Falls back to graceful shutdown if reload fails.\n process.on(\"SIGHUP\", () => {\n try {\n const newCfg = loadConfig();\n // Apply model change if present\n if (typeof newCfg.model === \"string\" && newCfg.model !== ctx.agentConfig.model) {\n ctx.agentConfig.model = newCfg.model;\n process.stderr.write(`[lynx] SIGHUP:模型已切换 → ${newCfg.model}\\n`);\n }\n // Theme changes are handled by the TUI on next render\n if (typeof newCfg.theme === \"string\") {\n process.stderr.write(`[lynx] SIGHUP:主题已切换 → ${newCfg.theme}\\n`);\n }\n process.stderr.write(\"[lynx] SIGHUP:配置已重新加载\\n\");\n } catch (reloadErr) {\n process.stderr.write(\n `[lynx] SIGHUP 重新加载失败:${reloadErr instanceof Error ? reloadErr.message : String(reloadErr)} — 回退到关闭流程\\n`,\n );\n gracefulShutdown();\n }\n });\n\n // ── Terminal loss detection ─────────────────────\n const onTerminalLost = () => {\n // stdin/stdout closed — terminal was killed\n startForceExitTimer();\n try {\n ctx.destroy();\n } catch {\n // Best effort\n }\n process.exit(0);\n };\n\n process.stdin.on(\"end\", onTerminalLost);\n process.stdin.on(\"close\", onTerminalLost);\n process.stdout.on(\"close\", onTerminalLost);\n\n // ── Unhandled rejection — log and exit ──────────\n process.on(\"unhandledRejection\", (reason) => {\n process.stderr.write(\n `[lynx] 未处理的 Promise 拒绝:${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}\\n`,\n );\n startForceExitTimer();\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n });\n\n // ── Uncaught exception — log and exit ───────────\n process.on(\"uncaughtException\", (err) => {\n process.stderr.write(`[lynx] 未捕获的异常:${err.stack ?? err.message}\\n`);\n startForceExitTimer();\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n });\n}\n\n/**\n * Register a cleanup handler that runs during graceful shutdown.\n * Replaces any previously registered handler.\n */\nexport function onCleanup(handler: () => void): void {\n _cleanupHandler = handler;\n}\n\n/**\n * Remove all process lifecycle handlers (for testing).\n */\nexport function uninstallProcessLifecycle(): void {\n installed = false;\n process.removeAllListeners(\"SIGINT\");\n process.removeAllListeners(\"SIGTERM\");\n process.removeAllListeners(\"SIGHUP\");\n if (hardExitTimer) {\n clearTimeout(hardExitTimer);\n hardExitTimer = null;\n }\n _cleanupHandler = null;\n}\n\n// ── Internals ──────────────────────────────────────────\n\nfunction startForceExitTimer(): void {\n if (hardExitTimer) return;\n hardExitTimer = setTimeout(() => {\n process.stderr.write(\"[lynx] 超时后强制退出\\n\");\n process.exit(1);\n }, FORCE_EXIT_MS);\n hardExitTimer.unref(); // Don't block the event loop\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAS,YAAe,UAAiC;CACvD,IAAI,CAAC,WAAW,QAAQ,GAAG,OAAO,KAAA;CAClC,IAAI;EACF,OAAO,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;CACnD,QAAQ;EACN;CACF;AACF;;;;;AAMA,SAAS,gBAAgB,UAAsC;CAC7D,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,IAAI,WAAW,KAAK,KAAK,OAAO,CAAC,GAAG,OAAO;EAC3C,MAAM,SAAS,KAAK,KAAK,IAAI;EAC7B,IAAI,WAAW,KAAK,OAAO,KAAA;EAC3B,MAAM;CACR;AAEF;;;;;;;AAUA,SAAgB,eAAe,cAAsC;CAInE,MAAM,iBAAiB,YAHT,aAGuC,CAAC,CAAC,YAAY;CAGnE,MAAM,cAAc,eAAe,gBAAgB,YAAY,IAAI,gBAAgB,QAAQ,IAAI,CAAC;CAChG,MAAM,kBAAkB,cACpB,YAA0B,KAAK,aAAa,SAAS,eAAe,CAAC,IACrE,KAAA;CAGJ,MAAM,gBAAgB,cAClB,YAA0B,KAAK,aAAa,SAAS,qBAAqB,CAAC,IAC3E,KAAA;CAGJ,MAAM,yBAAS,IAAI,IAA4B;CAE/C,SAAS,WAAW,SAAwC,QAA+B;EACzF,IAAI,CAAC,SAAS;EACd,KAAK,MAAM,UAAU,SACnB,OAAO,IAAI,OAAO,MAAM;GAAE,QAAQ;GAAQ;EAAO,CAAC;CAEtD;CAEA,WAAW,gBAAgB,YAAY,QAAQ;CAC/C,WAAW,iBAAiB,YAAY,SAAS;CACjD,WAAW,eAAe,YAAY,eAAe;CAErD,OAAO;EACL,SAAS,MAAM,KAAK,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,MAAM;EACxD,SAAS;CACX;AACF;;;;;;;AAQA,SAAgB,0BAA0B,cAA0C;CAElF,OADe,eAAe,YAClB,CAAC,CAAC,QAAQ,QAAQ,MAAM;EAClC,MAAM,UAAU,CAAC,EAAE,EAAE,WAAW,EAAE;EAClC,IAAI,CAAC,SAAS,CAEd;EACA,OAAO;CACT,CAAC;AACH;;;;ACyBA,MAAM,wBAAwB;;;;;;;;;;;;;;AAoB9B,SAAS,yBAA2C;CAClD,MAAM,0BAAU,IAAI,IAA+B;CACnD,IAAI,aAOO;CA0CX,OAAO;EAvCL,MAAM,kBAAkB,UAAU,QAAQ,aAA+B;GAEvE,IAAI,WAAW,UAAU,WAAW,iBAAiB,OAAO;GAE5D,OAAO,IAAI,SAAkB,YAAY;IACvC,MAAM,YAAY,WAAW;IAC7B,MAAM,QAAQ,iBAAiB;KAC7B,QAAQ,OAAO,SAAS;KACxB,QAAQ,KAAK;IACf,GAAG,qBAAqB;IAExB,QAAQ,IAAI,WAAW;KAAE;KAAS;IAAM,CAAC;IAGzC,IAAI,YACF,WAAW;KAAE;KAAW;KAAU;KAAa;IAAO,CAAC;SAClD;KAEL,aAAa,KAAK;KAClB,QAAQ,OAAO,SAAS;KACxB,QAAQ,KAAK;IACf;GACF,CAAC;EACH;EAEA,YAAY,WAAW,UAAgB;GACrC,MAAM,QAAQ,QAAQ,IAAI,SAAS;GACnC,IAAI,OAAO;IACT,aAAa,MAAM,KAAK;IACxB,QAAQ,OAAO,SAAS;IACxB,MAAM,QAAQ,QAAQ;GACxB;EACF;EAEA,cAAc,SAAe;GAC3B,aAAa;EACf;CAGU;AACd;;;;;AAQA,SAAS,qBAAqB,KAAuB;CACnD,MAAM,QAAkB,CAAC;CACzB,IAAI;CACJ,IAAI;EACF,UAAU,YAAY,GAAG;CAC3B,QAAQ;EACN,OAAO;CACT;CACA,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,CAAC,MAAM,SAAS,KAAK,GAAG;EAC5B,MAAM,WAAW,KAAK,KAAK,KAAK;EAChC,IAAI;GACF,IAAI,CAAC,SAAS,QAAQ,CAAC,CAAC,OAAO,GAAG;GAClC,MAAM,KAAK,aAAa,UAAU,OAAO,CAAC;EAC5C,QAAQ,CAER;CACF;CACA,OAAO;AACT;;;;;;;;AAWA,SAAgB,UAAU,QAAqC;CAC7D,MAAM,SAAS,KAAK,OAAO,SAAS,UAAU;CAC9C,MAAM,YAAY,OAAO,aAAa,QAAQ,IAAI;CAGlD,MAAM,KAAK,aAAa,EAAE,OAAO,CAAC;CAClC,QAAQ,EAAE;CAGV,MAAM,aAAa,qBAAqB,EAAE;CAC1C,MAAM,eAAe,mBAAmB;CACxC,MAAM,aAAa,iBAAiB;CACpC,MAAM,cAAc,kBAAkB,UAAU;CAGhD,qBAAqB,YAAY;CAMjC,MAAM,gBAAgB,oBADpB,OAAO,aAAa,KAAK,OAAO,SAAS,MAAM,YAAY,cAAc,QAAQ,CAChC;CAGnD,MAAM,gBAAgB,KAAK,QAAQ,GAAG,SAAS,QAAQ;CACvD,cAAc,aAAa,aAAa;CACxC,MAAM,mBAAmB,KAAK,WAAW,WAAW,QAAQ;CAC5D,cAAc,aAAa,gBAAgB;CAE3C,MAAM,SAAS,cAAc,KAAK;CA4BlC,aAAa,SAAS;EAxBpB,MAAM;EACN,aACE;EAEF,aAAa;GACX,MAAM;GACN,YAAY;IACV,QAAQ;KACN,MAAM;KACN,MAAM;MAAC;MAAQ;MAAQ;MAAU;KAAQ;KACzC,aACE;IACJ;IACA,OAAO;KAAE,MAAM;KAAU,aAAa;IAAuB;IAC7D,MAAM;KAAE,MAAM;KAAU,aAAa;IAAa;IAClD,OAAO;KAAE,MAAM;KAAU,aAAa;IAAsB;GAC9D;EACF;EACA,MAAM;EACN,QAAQ;EACR,cAAc,EAAE,MAAM,SAAS;EAC/B,UAAU;EACV,OAAO;CAE+B,GAAG,uBAAuB,aAAa,CAAC;CAGhF,MAAM,aAAa,iBAAiB;CAGpC,MAAM,qBAAqB,0BAA0B;CAIrD,MAAM,sBAAsB,OAAO,cAAc,CAAC;CAClD,MAAM,eAAe,mBAAmB,QACrC,MAAM,CAAC,oBAAoB,MAAM,MAAM,EAAE,SAAS,EAAE,IAAI,CAC3D;CACA,MAAM,gBAAgB,CAAC,GAAG,qBAAqB,GAAG,YAAY;CAG9D,KAAK,MAAM,gBAAgB,eACzB,WAAW,QAAQ,YAAY,CAAC,CAAC,YAAY,CAG7C,CAAC;CAIH,MAAM,kBAAkB,sBAAsB;CAG9C,IAAI,OAAO,QAAQ,SAAS,OAAO,QAAQ,WAAW;EACpD,MAAM,gBAAgB,oBAAoB,OAAO,MAAM;EACvD,gBAAgB,SAAS,aAAa;CACxC;CAIA,MAAM,+BAAe,IAAI,IAAyB;CAClD,MAAM,WAAW,aAAa,QAAQ;CACtC,KAAK,MAAM,QAAQ,UACjB,IAAI;EACF,MAAM,UAAU,aAAa,gBAAgB,IAAI;EACjD,aAAa,IAAI,KAAK,MAAM,OAAO;CACrC,QAAQ,CAER;CAKF,MAAM,gBAA6B,EACjC,MAAM,OAAO,YAAY,SAAS;EAChC,MAAM,SAAS,MAAM,WAAW,SAAS,WAAW,UAAU,WAAW,OAAO;EAChF,OAAO;GACL,SAAS,OAAO;GAChB,SAAS,CAAC,OAAO;EACnB;CACF,EACF;CAEA,KAAK,MAAM,WAAW,WAAW,YAAY,GAAG;EAC9C,IAAI,aAAa,IAAI,QAAQ,IAAI,GAAG;GAClC,QAAQ,OAAO,MAAM,qBAAqB,QAAQ,KAAK,kBAAkB;GACzE;EACF;EACA,aAAa,IAAI,QAAQ,MAAM,aAAa;CAC9C;CAIqB,IAAI,IAAI,WAAW,YAAY,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI,CAAC;CACxE,MAAM,kBAAkB,CACtB,GAAG,UACH,GAAG,WAAW,YAAY,CAAC,CAAC,QAAQ,MAAM,CAAC,SAAS,MAAM,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,CACpF;CAGA,MAAM,mBAAmB,uBAAuB;CAChD,MAAM,eAAe,mBAAmB;CAExC,MAAM,iBAAiB;EAAE;EAAkB,QADtB,mBACyC;EAAG,OAAO;CAAa;CAGrF,MAAM,mBAAmB,IAAI,iBAAiB,OAAO,aAAqB;EAGxE,OAAO,OAAO;CAChB,CAAC;CAGD,aAAa,aAAa,SAAS;CAGnC,MAAM,gBAAgB,oBAAoB;CAG1C,MAAM,mBAAmB,uBAAuB;CAGhD,MAAM,gBAAgB,KAAK,QAAQ,GAAG,SAAS,QAAQ;CACvD,MAAM,mBAAmB,KAAK,WAAW,WAAW,QAAQ;CAC5D,MAAM,gBAAgB,oBAAoB,aAAa;CACvD,MAAM,cAAc,CAClB,GAAI,cACD,KAAK,CAAC,CACN,KAAK,MAAM,cAAc,IAAI,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAC9C,OAAO,OAAO,GACjB,GAAG,qBAAqB,gBAAgB,CAC1C;CAGA,MAAM,qBAAqB,yBAAyB,aAAa;CACjE,aAAa,SAAS,uBAAuB,kBAAkB;CAC/D,aAAa,IAAI,sBAAsB,MAAM,kBAAkB;CAC/D,SAAS,KAAK,qBAAqB;CASnC,MAAM,iBAAiB,qBAAqB,EAL1C,WAAW,OAAO,eAAuB;EAEvC,OAAO,EAAE,SAAQ,MADE,WAAW,UAAU,UAAU,EAAA,CAC5B,OAAO;CAC/B,EAEmD,CAAC;CACtD,aAAa,SAAS,mBAAmB,cAAc;CACvD,aAAa,IAAI,kBAAkB,MAAM,cAAc;CACvD,SAAS,KAAK,iBAAiB;CAkB/B,MAAM,qBAAqB,yBAAyB,EAdlD,MAAM,KAAK,SAAiB,SAAiB,YAAuB;EAClE,MAAM,UAAU,gBAAgB,IAAI,OAAO;EAC3C,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,YAAY,SAAS;EACnD,MAAM,UAAU;GACd,IAAI,WAAW;GACf,MAAM;GACN,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM;GAAQ,CAAC;GAClD,WAAW,KAAK,IAAI;GACpB,WAAW;EACb;EAEA,OAAO,EAAE,WAAA,MADe,QAAQ,YAAY,OAAO,EAChC;CACrB,EAEkE,CAAC;CACrE,aAAa,SAAS,uBAAuB,kBAAkB;CAC/D,aAAa,IAAI,sBAAsB,MAAM,kBAAkB;CAC/D,SAAS,KAAK,qBAAqB;CAGnC,MAAM,eAAe,KAAK,QAAQ,GAAG,SAAS,SAAS;CACvD,MAAM,qBAA+B,CAAC;CACtC,IAAI;EACF,IAAI,WAAW,YAAY,KAAK,SAAS,YAAY,CAAC,CAAC,OAAO,GAC5D,mBAAmB,KAAK,uBAAuB,aAAa,cAAc,OAAO,GAAG;CAExF,QAAQ,CAER;CAEA,MAAM,eAAe,KAAK,QAAQ,GAAG,SAAS,OAAO;CACrD,MAAM,kBAAkB,KAAK,WAAW,WAAW,OAAO;CAC1D,MAAM,QAAQ;EACZ,GAAG;EACH,GAAG,qBAAqB,YAAY;EACpC,GAAG,qBAAqB,eAAe;CACzC;CAGA,MAAM,cAA2B;EAC/B,UAAU,OAAO;EACjB,OAAO,OAAO;EACd,cAAc,oBAAoB;EAClC,WAAW;EACX,uBAAuB;EACvB,QAAQ;GAAE,WAAW;GAAS,QAAQ;GAAI,UAAU;EAAI;CAC1D;CAEA,MAAM,SAAS,kBAAkB;EAC/B,QAAQ;EACR,UAAU,OAAO;EACjB;EACA,UAAU;EACV;EACA;EACA;EACA,iBAAiB,OAAO,UAAU,QAAQ,gBAAgB;GAExD,IAAI,cAAc,UAAU,GAAG,OAAO;GAEtC,MAAM,cAAc;GACpB,MAAM,WAAW,MAAM,iBAAiB,kBAAkB,UAAU,aAAa,WAAW;GAG5F,IAAI,gBAAgB,sBAAsB,gBAAgB,aACxD,IAAI,UACF,cAAc,eAAe;QAE7B,cAAc,aAAa;GAG/B,OAAO;EACT;CACF,CAAC;CAGD,MAAM,cAAc,kBAAkB;CACtC,kBAAkB,WAAW;CA+B7B,OAAO;EA5BL;EACA;EACA;EACA;EACA;EACA;EACA,UAAU,OAAO;EACjB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA,UAAgB;GACd,YAAY,QAAQ;GACpB,gBAAgB,WAAW;GAC3B,WAAW,QAAQ;GACnB,OAAO,QAAQ;GACf,WAAW,QAAQ;GACnB,GAAG,MAAM;EACX;CAGO;AACX;;;;;;;;;ACjgBA,SAAgB,YAA0B;CACxC,MAAM,UAAU,KAAK,IAAI;CAEzB,MAAM,QAAQ,aAAa;CAC3B,MAAM,KAAK,aAAa,EAAE,QAAQ,MAAM,QAAQ,CAAC;CACjD,QAAQ,EAAE;CAGV,OAAO;EAAE;EAAO;EAAI,WADF,KAAK,IAAI,IAAI;CACD;AAChC;;;;;;;;;;AAWA,eAAsB,UAAU,QAAmD;CACjF,OAAO,eAAe,MAAM,OAAO,KAAK;AAC1C;;;;;;;;;;;;;AAcA,eAAsB,qBACpB,KACA,OAC6B;CAC7B,OAAO,eAAe,KAAK,KAAK;AAClC;AASA,SAAS,qBAAmC;CAC1C,OAAO;EACL,YAAY,CAAC;EACb,OAAO,QAAQ,QAAQ,OAAO,MAAM,YAAY,IAAI,GAAG;CACzD;AACF;AAEA,eAAe,eACb,KACA,OAC6B;CAC7B,MAAM,SAAS,mBAAmB;CAClC,MAAM,UAA8B,CAAC;CAErC,MAAM,QAAoC;EACxC,YAAY;GACV,MAAM,eAAe,KAAK,OAAO,MAAM;EACzC;EACA,YAAY;GACV,MAAM,cAAc,KAAK,OAAO,MAAM;EACxC;EACA,YAAY;GACV,MAAM,kBAAkB,KAAK,MAAM;EACrC;EACA,YAAY;GACV,MAAM,WAAW,KAAK,OAAO,MAAM;EACrC;EACA,YAAY;GACV,MAAM,aAAa,KAAK,MAAM;EAChC;CACF;CAEA,KAAK,MAAM,QAAQ,OAAO;EAExB,MAAM,IAAI,SAAS,YAAY,aAAa,OAAO,CAAC;EAEpD,MAAM,UAAU,KAAK,IAAI;EACzB,MAAM,OAAO,KAAK,QAAQ;EAC1B,IAAI;GACF,MAAM,KAAK;GACX,QAAQ,KAAK;IAAE;IAAM,IAAI;IAAM,WAAW,KAAK,IAAI,IAAI;GAAQ,CAAC;EAClE,SAAS,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC/D,OAAO,KAAK,GAAG,KAAK,WAAW,SAAS;GACxC,QAAQ,KAAK;IAAE;IAAM,IAAI;IAAO,OAAO;IAAS,WAAW,KAAK,IAAI,IAAI;GAAQ,CAAC;EACnF;CACF;CAEA,IAAI,QAAQ,MAAM,MAAM,CAAC,EAAE,EAAE,GAAG;EAC9B,MAAM,WAAW,QACd,QAAQ,MAAM,CAAC,EAAE,EAAE,CAAC,CACpB,KAAK,MAAM,EAAE,IAAI,CAAC,CAClB,KAAK,IAAI;EACZ,OAAO,KAAK,oCAAoC,UAAU;CAC5D,OACE,OAAO,KAAK,sBAAsB,QAAQ,OAAO,UAAU;CAG7D,OAAO;AACT;;;;;;;;;;AAaA,eAAe,eACb,KACA,OACA,QACe;CACf,IAAI,CAAC,KAAK;CAEV,MAAM,EAAE,aAAa,aAAa,MAAM,OAAO;CAC/C,MAAM,EAAE,SAAS,MAAM,OAAO;CAE9B,MAAM,WAAW,CAAC,KAAK,MAAM,MAAM,YAAY,GAAG,KAAK,QAAQ,IAAI,GAAG,SAAS,YAAY,CAAC;CAE5F,KAAK,MAAM,OAAO,UAAU;EAC1B,IAAI;EACJ,IAAI;GACF,UAAU,YAAY,GAAG;EAC3B,QAAQ;GACN;EACF;EAEA,KAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,WAAW,KAAK,KAAK,KAAK;GAChC,IAAI;IACF,IAAI,CAAC,SAAS,QAAQ,CAAC,CAAC,YAAY,GAAG;GACzC,QAAQ;IACN;GACF;GAGA,MAAM,eAAe,KAAK,UAAU,eAAe;GACnD,IAAI;IACF,SAAS,YAAY;GACvB,QAAQ;IACN;GACF;GAEA,IAAI;IACF,MAAM,aAAa,IAAI,eAAe,iBAAiB,KAAK,CAAC,QAAQ,CAAC;IAEtE,KAAK,MAAM,iBAAiB,YAAY;KACtC,MAAM,YAAkC;MACtC,UAAU,cAAc,SAAS;MACjC,QAAQ;OACN,OAAO,QAAQ,OAAO,KAAK,WAAW,cAAc,SAAS,KAAK,IAAI,KAAK;OAC3E,OAAO,QAAQ,OAAO,KAAK,WAAW,cAAc,SAAS,KAAK,IAAI,KAAK;OAC3E,QAAQ,QAAQ,OAAO,KAAK,WAAW,cAAc,SAAS,KAAK,IAAI,KAAK;MAC9E;MACA,QAAQ,CAAC;MACT,SAAS,oBAAoB;KAC/B;KACA,IAAI,eAAe,OAAO,KAAK,eAAe,SAAS,CAAC,CAAC,OAAO,QAAQ;MACtE,OAAO,KAAK,WAAW,cAAc,SAAS,KAAK,oBAAoB,OAAO,GAAG,GAAG;KACtF,CAAC;IACH;GACF,QAAQ,CAER;EACF;CACF;AACF;;;;;;;AAQA,SAAS,sBAAuD;CAC9D,MAAM,wBAAQ,IAAI,IAAqB;CACvC,OAAO;EACL,IAAO,KAA4B;GACjC,OAAO,MAAM,IAAI,GAAG;EACtB;EACA,IAAO,KAAa,OAAgB;GAClC,MAAM,IAAI,KAAK,KAAK;EACtB;EACA,OAAO,KAAmB;GACxB,MAAM,OAAO,GAAG;EAClB;EACA,QAAc;GACZ,MAAM,MAAM;EACd;CACF;AACF;;;;;;;;AASA,eAAe,cACb,KACA,QACA,SACe;CACf,IAAI,CAAC,KAAK;CAEV,MAAM,SAAS,IAAI;CAGnB,OAAO,cAAc,GAAG;CACxB,OAAO,cAAc,GAAG;CAGxB,MAAM,OAAO,aAAa;AAC5B;;;;;;;;;;;AAYA,eAAe,kBAAkB,MAAyB,SAAsC,CAGhG;;;;;;;;;;;;;;AAeA,eAAe,WACb,MACA,OACA,SACe,CAIjB;;;;;;;;AASA,eAAe,aAAa,KAAwB,QAAqC;CACvF,IAAI,CAAC,KAAK;CACV,MAAM,aAAa,IAAI,gBAAgB,KAAK;CAC5C,IAAI,WAAW,WAAW,GAAG;EAC3B,OAAO,KAAK,wDAAwD;EACpE;CACF;CACA,OAAO,KAAK,0BAA0B,WAAW,KAAK,IAAI,GAAG;CAC7D,MAAM,IAAI,gBAAgB,QAAQ;CAClC,OAAO,KAAK,yBAAyB,WAAW,KAAK,IAAI,GAAG;AAC9D;;;;;;;;;;;;;;AC7TA,MAAM,YAAY;;AAElB,MAAM,WAAW;;AAGjB,MAAM,MAAM;;AAEZ,MAAM,MAAM;;AAKZ,SAAgB,kBAAwB;CACtC,QAAQ,OAAO,MAAM,SAAS;AAChC;;AAGA,SAAgB,iBAAuB;CACrC,QAAQ,OAAO,MAAM,QAAQ;AAC/B;;;;;;;;;;AAWA,IAAI,aAAa;AAEjB,SAAgB,YAAkB;CAChC,IAAI,YAAY;CAChB,aAAa;CACb,QAAQ,OAAO,MAAM,GAAG;AAC1B;AAEA,SAAgB,UAAgB;CAC9B,IAAI,CAAC,YAAY;CACjB,aAAa;CACb,QAAQ,OAAO,MAAM,GAAG;AAC1B;;;;;AAMA,SAAgB,SAAY,IAAgB;CAC1C,UAAU;CACV,IAAI;EACF,OAAO,GAAG;CACZ,UAAU;EACR,QAAQ;CACV;AACF;;;AC3BA,IAAI,gBAAuC;AAC3C,IAAI,YAAY;;AAGhB,MAAM,gBAAgB;;;;;;;;AAWtB,SAAgB,wBAAwB,QAA+B;CACrE,IAAI,WAAW;CACf,YAAY;CAEZ,MAAM,EAAE,KAAK,WAAW,aAAa,qBAAqB,gBAAgB;CAG1E,MAAM,iBAAiB;EACrB,MAAM,QAAQ,oBAAoB;EAElC,IAAI,SAAS,GAAG;GAEd,eAAe;GACf,IAAI,QAAQ;GACZ,QAAQ,KAAK,CAAC;EAChB;EAEA,IAAI,CAAC,YAAY,GAAG;GAElB,eAAe;GACf,IAAI,QAAQ;GACZ,QAAQ,KAAK,CAAC;EAChB;EAEA,IAAI,UAAU,GACZ,UAAU;OACL,IAAI,UAAU,GACnB,YAAY;CAEhB;CAEA,QAAQ,GAAG,UAAU,QAAQ;CAG7B,MAAM,yBAAyB;EAC7B,oBAAoB;EAEpB,IAAI;GACF,eAAe;EACjB,QAAQ,CAER;EAEA,IAAI;GACF,IAAI,QAAQ;EACd,QAAQ,CAER;EAEA,QAAQ,KAAK,CAAC;CAChB;CAEA,QAAQ,GAAG,WAAW,gBAAgB;CAKtC,QAAQ,GAAG,gBAAgB;EACzB,IAAI;GACF,MAAM,SAAS,WAAW;GAE1B,IAAI,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,IAAI,YAAY,OAAO;IAC9E,IAAI,YAAY,QAAQ,OAAO;IAC/B,QAAQ,OAAO,MAAM,yBAAyB,OAAO,MAAM,GAAG;GAChE;GAEA,IAAI,OAAO,OAAO,UAAU,UAC1B,QAAQ,OAAO,MAAM,yBAAyB,OAAO,MAAM,GAAG;GAEhE,QAAQ,OAAO,MAAM,yBAAyB;EAChD,SAAS,WAAW;GAClB,QAAQ,OAAO,MACb,wBAAwB,qBAAqB,QAAQ,UAAU,UAAU,OAAO,SAAS,EAAE,aAC7F;GACA,iBAAiB;EACnB;CACF,CAAC;CAGD,MAAM,uBAAuB;EAE3B,oBAAoB;EACpB,IAAI;GACF,IAAI,QAAQ;EACd,QAAQ,CAER;EACA,QAAQ,KAAK,CAAC;CAChB;CAEA,QAAQ,MAAM,GAAG,OAAO,cAAc;CACtC,QAAQ,MAAM,GAAG,SAAS,cAAc;CACxC,QAAQ,OAAO,GAAG,SAAS,cAAc;CAGzC,QAAQ,GAAG,uBAAuB,WAAW;EAC3C,QAAQ,OAAO,MACb,0BAA0B,kBAAkB,QAAS,OAAO,SAAS,OAAO,UAAW,OAAO,MAAM,EAAE,GACxG;EACA,oBAAoB;EACpB,eAAe;EACf,IAAI,QAAQ;EACZ,QAAQ,KAAK,CAAC;CAChB,CAAC;CAGD,QAAQ,GAAG,sBAAsB,QAAQ;EACvC,QAAQ,OAAO,MAAM,iBAAiB,IAAI,SAAS,IAAI,QAAQ,GAAG;EAClE,oBAAoB;EACpB,eAAe;EACf,IAAI,QAAQ;EACZ,QAAQ,KAAK,CAAC;CAChB,CAAC;AACH;;;;;AAMA,SAAgB,UAAU,SAA2B,CAErD;;;;AAKA,SAAgB,4BAAkC;CAChD,YAAY;CACZ,QAAQ,mBAAmB,QAAQ;CACnC,QAAQ,mBAAmB,SAAS;CACpC,QAAQ,mBAAmB,QAAQ;CACnC,IAAI,eAAe;EACjB,aAAa,aAAa;EAC1B,gBAAgB;CAClB;AAEF;AAIA,SAAS,sBAA4B;CACnC,IAAI,eAAe;CACnB,gBAAgB,iBAAiB;EAC/B,QAAQ,OAAO,MAAM,kBAAkB;EACvC,QAAQ,KAAK,CAAC;CAChB,GAAG,aAAa;CAChB,cAAc,MAAM;AACtB"}
|