@24klynx/cli 0.1.3 → 0.1.5
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/LICENSE +21 -0
- package/dist/{config-Braj-aBw.mjs → config-B-iYJluO.mjs} +11 -4
- package/dist/config-B-iYJluO.mjs.map +1 -0
- package/dist/config-C6StrUyB.mjs +2 -0
- package/dist/git-branch-Dn1CP6An.mjs.map +1 -1
- package/dist/{headless-launcher-CnSrw3bm.mjs → headless-launcher-faY5GV9T.mjs} +3 -3
- package/dist/headless-launcher-faY5GV9T.mjs.map +1 -0
- package/dist/index.d.mts +30 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +12 -8
- package/dist/index.mjs.map +1 -1
- package/dist/privacy-B6Rm1Xck.mjs.map +1 -1
- package/dist/{process-lifecycle-kdV53L3o.mjs → process-lifecycle-Xy1A3HFe.mjs} +22 -3
- package/dist/process-lifecycle-Xy1A3HFe.mjs.map +1 -0
- package/dist/sandbox-toggle-9akjTw3h.mjs.map +1 -1
- package/dist/upgrade-CREWRNeC.mjs.map +1 -1
- package/package.json +19 -19
- package/dist/config-Braj-aBw.mjs.map +0 -1
- package/dist/config-D9DbomLt.mjs +0 -2
- package/dist/headless-launcher-CnSrw3bm.mjs.map +0 -1
- package/dist/process-lifecycle-kdV53L3o.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"privacy-B6Rm1Xck.mjs","names":[],"sources":["../src/commands/privacy.ts"],"sourcesContent":["/**\n * /privacy-settings — 隐私设置面板。\n *\n * 管理允许/禁止的域名列表和数据分析共享选项。\n * 配置文件位于 ~/.lynx/privacy.json。\n *\n * 支持三种操作:\n * - 无参数:显示当前隐私设置\n * - --allow <domain>:添加域名到允许列表\n * - --deny <domain>:添加域名到禁止列表\n */\n\nimport { homedir } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface PrivacyCommandArgs {\n /** 显示当前隐私设置。 */\n show?: boolean;\n /** 将域名添加到允许列表。 */\n allow?: string;\n /** 将域名添加到禁止列表。 */\n deny?: string;\n}\n\ninterface PrivacyConfig {\n /** 允许访问的域名列表。 */\n allowlist: string[];\n /** 禁止访问的域名列表。 */\n denylist: string[];\n /** 是否共享匿名使用分析数据。 */\n shareAnalytics: boolean;\n}\n\n// ── Constants ────────────────────────────────────────\n\nconst DEFAULT_CONFIG: PrivacyConfig = {\n allowlist: [],\n denylist: [],\n shareAnalytics: false,\n};\n\n// ── Helpers ──────────────────────────────────────────\n\n/** 获取 privacy.json 的完整路径。 */\nfunction privacyConfigPath(): string {\n return join(homedir(), \".lynx\", \"privacy.json\");\n}\n\n/** 读取当前隐私配置。文件不存在时返回默认值。 */\nfunction loadPrivacyConfig(): PrivacyConfig {\n const path = privacyConfigPath();\n if (!existsSync(path)) return { ...DEFAULT_CONFIG };\n try {\n const raw = readFileSync(path, \"utf-8\");\n const parsed = JSON.parse(raw);\n return {\n allowlist: Array.isArray(parsed?.allowlist) ? parsed.allowlist : [],\n denylist: Array.isArray(parsed?.denylist) ? parsed.denylist : [],\n shareAnalytics: typeof parsed?.shareAnalytics === \"boolean\" ? parsed.shareAnalytics : false,\n };\n } catch {\n return { ...DEFAULT_CONFIG };\n }\n}\n\n/** 保存隐私配置到磁盘。 */\nfunction savePrivacyConfig(config: PrivacyConfig): void {\n const path = privacyConfigPath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { mode: 0o700, recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/** 验证域名格式(基本检查:不含空格、含至少一个点)。 */\nfunction isValidDomain(domain: string): boolean {\n return domain.length > 0 && !domain.includes(\" \") && domain.includes(\".\");\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /privacy-settings 命令。\n *\n * 管理允许/禁止的域名列表,显示或更新隐私设置。\n */\nexport function handlePrivacyCommand(args: PrivacyCommandArgs): { output: string } {\n const config = loadPrivacyConfig();\n\n // 添加域名到允许列表\n if (args.allow) {\n if (!isValidDomain(args.allow)) {\n return { output: `错误:「${args.allow}」不是有效的域名格式。` };\n }\n if (config.allowlist.includes(args.allow)) {\n return { output: `域名 \"${args.allow}\" 已在允许列表中。` };\n }\n // 从禁止列表中移除(如果存在)\n config.denylist = config.denylist.filter((d) => d !== args.allow);\n config.allowlist.push(args.allow);\n savePrivacyConfig(config);\n return {\n output: `已将 \"${args.allow}\" 添加到允许列表。\\n\\n${formatSettings(config)}`,\n };\n }\n\n // 添加域名到禁止列表\n if (args.deny) {\n if (!isValidDomain(args.deny)) {\n return { output: `错误:「${args.deny}」不是有效的域名格式。` };\n }\n if (config.denylist.includes(args.deny)) {\n return { output: `域名 \"${args.deny}\" 已在禁止列表中。` };\n }\n // 从允许列表中移除(如果存在)\n config.allowlist = config.allowlist.filter((d) => d !== args.deny);\n config.denylist.push(args.deny);\n savePrivacyConfig(config);\n return {\n output: `已将 \"${args.deny}\" 添加到禁止列表。\\n\\n${formatSettings(config)}`,\n };\n }\n\n // 默认:显示当前设置\n return { output: formatSettings(config) + \"\\n\\n\" + PRIVACY_POLICY };\n}\n\n// ── Formatting ───────────────────────────────────────\n\n/** 格式化隐私设置输出。 */\nfunction formatSettings(config: PrivacyConfig): string {\n const lines: string[] = [\n \"【隐私设置】\",\n \"\",\n \"允许列表(allowlist):\",\n ...(config.allowlist.length > 0\n ? config.allowlist.map((d) => ` + ${d}`)\n : [\" (空 — 无额外允许的域名)\"]),\n \"\",\n \"禁止列表(denylist):\",\n ...(config.denylist.length > 0\n ? config.denylist.map((d) => ` - ${d}`)\n : [\" (空 — 无额外禁止的域名)\"]),\n \"\",\n `分析数据共享:${config.shareAnalytics ? \"已启用\" : \"已禁用\"}`,\n \"\",\n \"操作提示:\",\n \" lynx privacy --allow <域名> 添加到允许列表\",\n \" lynx privacy --deny <域名> 添加到禁止列表\",\n ];\n return lines.join(\"\\n\");\n}\n\n/** 隐私政策简短摘要。 */\nconst PRIVACY_POLICY = [\n \"【数据隐私说明】\",\n \"\",\n \"Lynx 处理的数据类型:\",\n \" - 对话记录:存储在 ~/.lynx/sessions/ 目录,仅本地保存\",\n \" - 工具调用日志:存储在 ~/.lynx/logs/ 目录\",\n \" - 配置文件:存储在 ~/.lynx/config.json\",\n \"\",\n \"Lynx 不会自动上传数据到任何远程服务器。\",\n \"分析数据共享默认关闭,必须手动启用。\",\n \"你可以通过 allowlist/denylist 控制工具可访问的域名。\",\n].join(\"\\n\");\n"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"privacy-B6Rm1Xck.mjs","names":[],"sources":["../src/commands/privacy.ts"],"sourcesContent":["/**\n * /privacy-settings — 隐私设置面板。\n *\n * 管理允许/禁止的域名列表和数据分析共享选项。\n * 配置文件位于 ~/.lynx/privacy.json。\n *\n * 支持三种操作:\n * - 无参数:显示当前隐私设置\n * - --allow <domain>:添加域名到允许列表\n * - --deny <domain>:添加域名到禁止列表\n */\n\nimport { homedir } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Arguments for /privacy-settings — show/allow/deny operations for domain whitelist/blocklist. */\nexport interface PrivacyCommandArgs {\n /** 显示当前隐私设置。 */\n show?: boolean;\n /** 将域名添加到允许列表。 */\n allow?: string;\n /** 将域名添加到禁止列表。 */\n deny?: string;\n}\n\ninterface PrivacyConfig {\n /** 允许访问的域名列表。 */\n allowlist: string[];\n /** 禁止访问的域名列表。 */\n denylist: string[];\n /** 是否共享匿名使用分析数据。 */\n shareAnalytics: boolean;\n}\n\n// ── Constants ────────────────────────────────────────\n\nconst DEFAULT_CONFIG: PrivacyConfig = {\n allowlist: [],\n denylist: [],\n shareAnalytics: false,\n};\n\n// ── Helpers ──────────────────────────────────────────\n\n/** 获取 privacy.json 的完整路径。 */\nfunction privacyConfigPath(): string {\n return join(homedir(), \".lynx\", \"privacy.json\");\n}\n\n/** 读取当前隐私配置。文件不存在时返回默认值。 */\nfunction loadPrivacyConfig(): PrivacyConfig {\n const path = privacyConfigPath();\n if (!existsSync(path)) return { ...DEFAULT_CONFIG };\n try {\n const raw = readFileSync(path, \"utf-8\");\n const parsed = JSON.parse(raw);\n return {\n allowlist: Array.isArray(parsed?.allowlist) ? parsed.allowlist : [],\n denylist: Array.isArray(parsed?.denylist) ? parsed.denylist : [],\n shareAnalytics: typeof parsed?.shareAnalytics === \"boolean\" ? parsed.shareAnalytics : false,\n };\n } catch {\n return { ...DEFAULT_CONFIG };\n }\n}\n\n/** 保存隐私配置到磁盘。 */\nfunction savePrivacyConfig(config: PrivacyConfig): void {\n const path = privacyConfigPath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { mode: 0o700, recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/** 验证域名格式(基本检查:不含空格、含至少一个点)。 */\nfunction isValidDomain(domain: string): boolean {\n return domain.length > 0 && !domain.includes(\" \") && domain.includes(\".\");\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /privacy-settings 命令。\n *\n * 管理允许/禁止的域名列表,显示或更新隐私设置。\n */\nexport function handlePrivacyCommand(args: PrivacyCommandArgs): { output: string } {\n const config = loadPrivacyConfig();\n\n // 添加域名到允许列表\n if (args.allow) {\n if (!isValidDomain(args.allow)) {\n return { output: `错误:「${args.allow}」不是有效的域名格式。` };\n }\n if (config.allowlist.includes(args.allow)) {\n return { output: `域名 \"${args.allow}\" 已在允许列表中。` };\n }\n // 从禁止列表中移除(如果存在)\n config.denylist = config.denylist.filter((d) => d !== args.allow);\n config.allowlist.push(args.allow);\n savePrivacyConfig(config);\n return {\n output: `已将 \"${args.allow}\" 添加到允许列表。\\n\\n${formatSettings(config)}`,\n };\n }\n\n // 添加域名到禁止列表\n if (args.deny) {\n if (!isValidDomain(args.deny)) {\n return { output: `错误:「${args.deny}」不是有效的域名格式。` };\n }\n if (config.denylist.includes(args.deny)) {\n return { output: `域名 \"${args.deny}\" 已在禁止列表中。` };\n }\n // 从允许列表中移除(如果存在)\n config.allowlist = config.allowlist.filter((d) => d !== args.deny);\n config.denylist.push(args.deny);\n savePrivacyConfig(config);\n return {\n output: `已将 \"${args.deny}\" 添加到禁止列表。\\n\\n${formatSettings(config)}`,\n };\n }\n\n // 默认:显示当前设置\n return { output: formatSettings(config) + \"\\n\\n\" + PRIVACY_POLICY };\n}\n\n// ── Formatting ───────────────────────────────────────\n\n/** 格式化隐私设置输出。 */\nfunction formatSettings(config: PrivacyConfig): string {\n const lines: string[] = [\n \"【隐私设置】\",\n \"\",\n \"允许列表(allowlist):\",\n ...(config.allowlist.length > 0\n ? config.allowlist.map((d) => ` + ${d}`)\n : [\" (空 — 无额外允许的域名)\"]),\n \"\",\n \"禁止列表(denylist):\",\n ...(config.denylist.length > 0\n ? config.denylist.map((d) => ` - ${d}`)\n : [\" (空 — 无额外禁止的域名)\"]),\n \"\",\n `分析数据共享:${config.shareAnalytics ? \"已启用\" : \"已禁用\"}`,\n \"\",\n \"操作提示:\",\n \" lynx privacy --allow <域名> 添加到允许列表\",\n \" lynx privacy --deny <域名> 添加到禁止列表\",\n ];\n return lines.join(\"\\n\");\n}\n\n/** 隐私政策简短摘要。 */\nconst PRIVACY_POLICY = [\n \"【数据隐私说明】\",\n \"\",\n \"Lynx 处理的数据类型:\",\n \" - 对话记录:存储在 ~/.lynx/sessions/ 目录,仅本地保存\",\n \" - 工具调用日志:存储在 ~/.lynx/logs/ 目录\",\n \" - 配置文件:存储在 ~/.lynx/config.json\",\n \"\",\n \"Lynx 不会自动上传数据到任何远程服务器。\",\n \"分析数据共享默认关闭,必须手动启用。\",\n \"你可以通过 allowlist/denylist 控制工具可访问的域名。\",\n].join(\"\\n\");\n"],"mappings":";;;;;;;;;;;;;;;AAuCA,MAAM,iBAAgC;CACpC,WAAW,CAAC;CACZ,UAAU,CAAC;CACX,gBAAgB;AAClB;;AAKA,SAAS,oBAA4B;CACnC,OAAO,KAAK,QAAQ,GAAG,SAAS,cAAc;AAChD;;AAGA,SAAS,oBAAmC;CAC1C,MAAM,OAAO,kBAAkB;CAC/B,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO,EAAE,GAAG,eAAe;CAClD,IAAI;EACF,MAAM,MAAM,aAAa,MAAM,OAAO;EACtC,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,OAAO;GACL,WAAW,MAAM,QAAQ,QAAQ,SAAS,IAAI,OAAO,YAAY,CAAC;GAClE,UAAU,MAAM,QAAQ,QAAQ,QAAQ,IAAI,OAAO,WAAW,CAAC;GAC/D,gBAAgB,OAAO,QAAQ,mBAAmB,YAAY,OAAO,iBAAiB;EACxF;CACF,QAAQ;EACN,OAAO,EAAE,GAAG,eAAe;CAC7B;AACF;;AAGA,SAAS,kBAAkB,QAA6B;CACtD,MAAM,OAAO,kBAAkB;CAC/B,MAAM,MAAM,QAAQ,IAAI;CACxB,IAAI,CAAC,WAAW,GAAG,GAAG,UAAU,KAAK;EAAE,MAAM;EAAO,WAAW;CAAK,CAAC;CACrE,cAAc,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO;AACrE;;AAGA,SAAS,cAAc,QAAyB;CAC9C,OAAO,OAAO,SAAS,KAAK,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,SAAS,GAAG;AAC1E;;;;;;AASA,SAAgB,qBAAqB,MAA8C;CACjF,MAAM,SAAS,kBAAkB;CAGjC,IAAI,KAAK,OAAO;EACd,IAAI,CAAC,cAAc,KAAK,KAAK,GAC3B,OAAO,EAAE,QAAQ,OAAO,KAAK,MAAM,aAAa;EAElD,IAAI,OAAO,UAAU,SAAS,KAAK,KAAK,GACtC,OAAO,EAAE,QAAQ,OAAO,KAAK,MAAM,YAAY;EAGjD,OAAO,WAAW,OAAO,SAAS,QAAQ,MAAM,MAAM,KAAK,KAAK;EAChE,OAAO,UAAU,KAAK,KAAK,KAAK;EAChC,kBAAkB,MAAM;EACxB,OAAO,EACL,QAAQ,OAAO,KAAK,MAAM,gBAAgB,eAAe,MAAM,IACjE;CACF;CAGA,IAAI,KAAK,MAAM;EACb,IAAI,CAAC,cAAc,KAAK,IAAI,GAC1B,OAAO,EAAE,QAAQ,OAAO,KAAK,KAAK,aAAa;EAEjD,IAAI,OAAO,SAAS,SAAS,KAAK,IAAI,GACpC,OAAO,EAAE,QAAQ,OAAO,KAAK,KAAK,YAAY;EAGhD,OAAO,YAAY,OAAO,UAAU,QAAQ,MAAM,MAAM,KAAK,IAAI;EACjE,OAAO,SAAS,KAAK,KAAK,IAAI;EAC9B,kBAAkB,MAAM;EACxB,OAAO,EACL,QAAQ,OAAO,KAAK,KAAK,gBAAgB,eAAe,MAAM,IAChE;CACF;CAGA,OAAO,EAAE,QAAQ,eAAe,MAAM,IAAI,SAAS,eAAe;AACpE;;AAKA,SAAS,eAAe,QAA+B;CAoBrD,OAAO;EAlBL;EACA;EACA;EACA,GAAI,OAAO,UAAU,SAAS,IAC1B,OAAO,UAAU,KAAK,MAAM,OAAO,GAAG,IACtC,CAAC,kBAAkB;EACvB;EACA;EACA,GAAI,OAAO,SAAS,SAAS,IACzB,OAAO,SAAS,KAAK,MAAM,OAAO,GAAG,IACrC,CAAC,kBAAkB;EACvB;EACA,UAAU,OAAO,iBAAiB,QAAQ;EAC1C;EACA;EACA;EACA;CAES,CAAC,CAAC,KAAK,IAAI;AACxB;;AAGA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as loadConfig } from "./config-
|
|
1
|
+
import { n as loadConfig } from "./config-B-iYJluO.mjs";
|
|
2
2
|
import { migrate, openDatabase, resolvePaths } from "@24klynx/core";
|
|
3
3
|
import { createSessionManager } from "@24klynx/session";
|
|
4
|
-
import { createMcpAuthHandler, createMemoryWriteHandler, createSendMessageHandler, createToolRegistry, injectTaskManager, mcpAuthDescriptor, memoryWriteDescriptor, registerBuiltinTools, sendMessageDescriptor } from "@24klynx/tools";
|
|
4
|
+
import { createListMcpResourcesHandler, createMcpAuthHandler, createMcpCallToolHandler, createMemoryWriteHandler, createReadMcpResourceHandler, createSendMessageHandler, createToolRegistry, createToolSearchHandler, injectTaskManager, listMcpResourcesDescriptor, mcpAuthDescriptor, mcpCallToolDescriptor, memoryWriteDescriptor, readMcpResourceDescriptor, registerBuiltinTools, sendMessageDescriptor, toolSearchDescriptor } from "@24klynx/tools";
|
|
5
5
|
import { PredictiveLoader, createHookRegistry, createManifestRegistry, createPluginLoader } from "@24klynx/plugins";
|
|
6
6
|
import { createMcpManager, createMemoryManager, createQueryEngine, createSkillRegistry, createSkillToolHandler, createTaskManager, getBaseSystemPrompt } from "@24klynx/agent";
|
|
7
7
|
import { createDenialTracker, createRuleEngine, createRulesLoader, loadFromDisk } from "@24klynx/permissions";
|
|
@@ -303,6 +303,22 @@ function bootstrap(config) {
|
|
|
303
303
|
toolRegistry.register(sendMessageDescriptor, sendMessageHandler);
|
|
304
304
|
toolHandlers.set(sendMessageDescriptor.name, sendMessageHandler);
|
|
305
305
|
allTools.push(sendMessageDescriptor);
|
|
306
|
+
const toolSearchHandler = createToolSearchHandler(() => toolRegistry.listAll());
|
|
307
|
+
toolRegistry.register(toolSearchDescriptor, toolSearchHandler);
|
|
308
|
+
toolHandlers.set(toolSearchDescriptor.name, toolSearchHandler);
|
|
309
|
+
allTools.push(toolSearchDescriptor);
|
|
310
|
+
const mcpCallToolHandler = createMcpCallToolHandler(mcpManager);
|
|
311
|
+
toolRegistry.register(mcpCallToolDescriptor, mcpCallToolHandler);
|
|
312
|
+
toolHandlers.set(mcpCallToolDescriptor.name, mcpCallToolHandler);
|
|
313
|
+
allTools.push(mcpCallToolDescriptor);
|
|
314
|
+
const listMcpResourcesHandler = createListMcpResourcesHandler(mcpManager);
|
|
315
|
+
toolRegistry.register(listMcpResourcesDescriptor, listMcpResourcesHandler);
|
|
316
|
+
toolHandlers.set(listMcpResourcesDescriptor.name, listMcpResourcesHandler);
|
|
317
|
+
allTools.push(listMcpResourcesDescriptor);
|
|
318
|
+
const readMcpResourceHandler = createReadMcpResourceHandler(mcpManager);
|
|
319
|
+
toolRegistry.register(readMcpResourceDescriptor, readMcpResourceHandler);
|
|
320
|
+
toolHandlers.set(readMcpResourceDescriptor.name, readMcpResourceHandler);
|
|
321
|
+
allTools.push(readMcpResourceDescriptor);
|
|
306
322
|
const globalLynxMd = join(homedir(), ".lynx", "LYNX.md");
|
|
307
323
|
const globalInstructions = [];
|
|
308
324
|
try {
|
|
@@ -417,6 +433,7 @@ async function runPhase2(result) {
|
|
|
417
433
|
* 3. connectMcpServers — establish MCP connections
|
|
418
434
|
* 4. loadMemory — load memory files into the agent context
|
|
419
435
|
*/
|
|
436
|
+
/** Execute Phase 2 background tasks (plugin scan, MCP connect, memory load) against an existing AppContext. */
|
|
420
437
|
async function runPhase2WithContext(ctx, paths) {
|
|
421
438
|
return runPhase2Tasks(ctx, paths);
|
|
422
439
|
}
|
|
@@ -647,11 +664,13 @@ function exitFullscreen() {
|
|
|
647
664
|
* is a no‑op (tracked via module‑level flag).
|
|
648
665
|
*/
|
|
649
666
|
let syncActive = false;
|
|
667
|
+
/** Enter synchronous terminal mode — flushes stdout and begins screen sync protocol (DCS). */
|
|
650
668
|
function beginSync() {
|
|
651
669
|
if (syncActive) return;
|
|
652
670
|
syncActive = true;
|
|
653
671
|
process.stdout.write(BSU);
|
|
654
672
|
}
|
|
673
|
+
/** Exit synchronous terminal mode — ends the screen sync protocol and restores normal output. */
|
|
655
674
|
function endSync() {
|
|
656
675
|
if (!syncActive) return;
|
|
657
676
|
syncActive = false;
|
|
@@ -781,4 +800,4 @@ function startForceExitTimer() {
|
|
|
781
800
|
//#endregion
|
|
782
801
|
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
802
|
|
|
784
|
-
//# sourceMappingURL=process-lifecycle-
|
|
803
|
+
//# sourceMappingURL=process-lifecycle-Xy1A3HFe.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-lifecycle-Xy1A3HFe.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\n/** A server config paired with its discovery source (global, project, or project-local). */\nexport interface McpServerEntry {\n config: McpServerConfig;\n source: McpConfigSource;\n}\n\n/** Result of loading MCP server configs — merged server list plus per-server metadata keyed by name. */\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 toolSearchDescriptor,\n createToolSearchHandler,\n mcpCallToolDescriptor,\n createMcpCallToolHandler,\n listMcpResourcesDescriptor,\n createListMcpResourcesHandler,\n readMcpResourceDescriptor,\n createReadMcpResourceHandler,\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\n/** Assembled application context — all singletons and services after Phase 1 + Phase 2 boot. */\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\n/** Bootstrap configuration — the minimal inputs needed to assemble the full AppContext. */\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 // Register ToolSearchTool — 语义搜索可用工具,需要注入工具列表\n const toolSearchHandler = createToolSearchHandler(() => toolRegistry.listAll());\n toolRegistry.register(toolSearchDescriptor, toolSearchHandler);\n toolHandlers.set(toolSearchDescriptor.name, toolSearchHandler);\n allTools.push(toolSearchDescriptor);\n\n // Register mcp__call_tool — 显式调用 MCP 服务器工具,需要 McpManager 注入\n const mcpCallToolHandler = createMcpCallToolHandler(mcpManager);\n toolRegistry.register(mcpCallToolDescriptor, mcpCallToolHandler);\n toolHandlers.set(mcpCallToolDescriptor.name, mcpCallToolHandler);\n allTools.push(mcpCallToolDescriptor);\n\n // Register ListMcpResourcesTool — 列出 MCP 服务器资源,需要 McpManager 注入\n const listMcpResourcesHandler = createListMcpResourcesHandler(mcpManager);\n toolRegistry.register(listMcpResourcesDescriptor, listMcpResourcesHandler);\n toolHandlers.set(listMcpResourcesDescriptor.name, listMcpResourcesHandler);\n allTools.push(listMcpResourcesDescriptor);\n\n // Register ReadMcpResourceTool — 通过 URI 读取 MCP 资源,需要 McpManager 注入\n const readMcpResourceHandler = createReadMcpResourceHandler(mcpManager);\n toolRegistry.register(readMcpResourceDescriptor, readMcpResourceHandler);\n toolHandlers.set(readMcpResourceDescriptor.name, readMcpResourceHandler);\n allTools.push(readMcpResourceDescriptor);\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\n/** Phase 1 blocking result — paths, open database handle, and resolved config for immediate use. */\nexport interface Phase1Result {\n paths: LynxPaths;\n db: Database;\n /** Wall‑clock ms for Phase 1. */\n elapsedMs: number;\n}\n\n/** Full startup result — Phase 1 state plus assembled AppContext and Phase 2 task outcomes. */\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 */\n/** Execute Phase 2 background tasks (plugin scan, MCP connect, memory load) against an existing AppContext. */\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\n/** Enter synchronous terminal mode — flushes stdout and begins screen sync protocol (DCS). */\nexport function beginSync(): void {\n if (syncActive) return;\n syncActive = true;\n process.stdout.write(BSU);\n}\n\n/** Exit synchronous terminal mode — ends the screen sync protocol and restores normal output. */\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\n/** Configuration for graceful shutdown — context, abort callbacks, and signal handlers. */\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox-toggle-9akjTw3h.mjs","names":[],"sources":["../src/commands/sandbox-toggle.ts"],"sourcesContent":["/**\n * /sandbox-toggle — 切换 Bash 沙箱模式开关。\n *\n * 读写 ~/.lynx/sandbox.json 文件,控制命令执行沙箱的启用状态。\n * 支持三种模式:\n * - 无参数:切换当前状态\n * - --enable:强制启用\n * - --disable:强制禁用\n *\n * 沙箱文件格式:{ \"enabled\": true }\n */\n\nimport { homedir } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface SandboxToggleArgs {\n /** 强制启用沙箱。 */\n enable?: boolean;\n /** 强制禁用沙箱。 */\n disable?: boolean;\n}\n\ninterface SandboxConfig {\n enabled: boolean;\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/** 获取 sandbox.json 的完整路径。 */\nfunction sandboxConfigPath(): string {\n return join(homedir(), \".lynx\", \"sandbox.json\");\n}\n\n/** 读取当前沙箱配置。文件不存在时默认启用。 */\nfunction loadSandboxConfig(): SandboxConfig {\n const path = sandboxConfigPath();\n if (!existsSync(path)) return { enabled: true };\n try {\n const raw = readFileSync(path, \"utf-8\");\n const parsed = JSON.parse(raw);\n return {\n enabled: typeof parsed?.enabled === \"boolean\" ? parsed.enabled : true,\n };\n } catch {\n return { enabled: true };\n }\n}\n\n/** 保存沙箱配置到磁盘。 */\nfunction saveSandboxConfig(config: SandboxConfig): void {\n const path = sandboxConfigPath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { mode: 0o700, recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /sandbox-toggle 命令。\n *\n * 根据参数切换、启用或禁用 Bash 沙箱。\n * 返回操作后的当前状态。\n */\nexport function handleSandboxToggleCommand(args: SandboxToggleArgs): { output: string } {\n const current = loadSandboxConfig();\n let newState: boolean;\n\n if (args.enable) {\n newState = true;\n } else if (args.disable) {\n newState = false;\n } else {\n // 无参数:切换\n newState = !current.enabled;\n }\n\n saveSandboxConfig({ enabled: newState });\n\n const statusText = newState ? \"已启用\" : \"已禁用\";\n const statusIcon = newState ? \"开\" : \"关\";\n const previousText = current.enabled ? \"启用\" : \"禁用\";\n\n if (args.enable || args.disable) {\n return {\n output: `沙箱模式:${statusIcon}\\n状态已从「${previousText}」切换为「${statusText}」。`,\n };\n }\n\n return {\n output: `沙箱模式:${statusIcon}\\n状态已切换:${previousText} → ${statusText}。`,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"sandbox-toggle-9akjTw3h.mjs","names":[],"sources":["../src/commands/sandbox-toggle.ts"],"sourcesContent":["/**\n * /sandbox-toggle — 切换 Bash 沙箱模式开关。\n *\n * 读写 ~/.lynx/sandbox.json 文件,控制命令执行沙箱的启用状态。\n * 支持三种模式:\n * - 无参数:切换当前状态\n * - --enable:强制启用\n * - --disable:强制禁用\n *\n * 沙箱文件格式:{ \"enabled\": true }\n */\n\nimport { homedir } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Arguments for /sandbox-toggle — enable/disable flags for the Bash sandbox. */\nexport interface SandboxToggleArgs {\n /** 强制启用沙箱。 */\n enable?: boolean;\n /** 强制禁用沙箱。 */\n disable?: boolean;\n}\n\ninterface SandboxConfig {\n enabled: boolean;\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/** 获取 sandbox.json 的完整路径。 */\nfunction sandboxConfigPath(): string {\n return join(homedir(), \".lynx\", \"sandbox.json\");\n}\n\n/** 读取当前沙箱配置。文件不存在时默认启用。 */\nfunction loadSandboxConfig(): SandboxConfig {\n const path = sandboxConfigPath();\n if (!existsSync(path)) return { enabled: true };\n try {\n const raw = readFileSync(path, \"utf-8\");\n const parsed = JSON.parse(raw);\n return {\n enabled: typeof parsed?.enabled === \"boolean\" ? parsed.enabled : true,\n };\n } catch {\n return { enabled: true };\n }\n}\n\n/** 保存沙箱配置到磁盘。 */\nfunction saveSandboxConfig(config: SandboxConfig): void {\n const path = sandboxConfigPath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { mode: 0o700, recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /sandbox-toggle 命令。\n *\n * 根据参数切换、启用或禁用 Bash 沙箱。\n * 返回操作后的当前状态。\n */\nexport function handleSandboxToggleCommand(args: SandboxToggleArgs): { output: string } {\n const current = loadSandboxConfig();\n let newState: boolean;\n\n if (args.enable) {\n newState = true;\n } else if (args.disable) {\n newState = false;\n } else {\n // 无参数:切换\n newState = !current.enabled;\n }\n\n saveSandboxConfig({ enabled: newState });\n\n const statusText = newState ? \"已启用\" : \"已禁用\";\n const statusIcon = newState ? \"开\" : \"关\";\n const previousText = current.enabled ? \"启用\" : \"禁用\";\n\n if (args.enable || args.disable) {\n return {\n output: `沙箱模式:${statusIcon}\\n状态已从「${previousText}」切换为「${statusText}」。`,\n };\n }\n\n return {\n output: `沙箱模式:${statusIcon}\\n状态已切换:${previousText} → ${statusText}。`,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,SAAS,oBAA4B;CACnC,OAAO,KAAK,QAAQ,GAAG,SAAS,cAAc;AAChD;;AAGA,SAAS,oBAAmC;CAC1C,MAAM,OAAO,kBAAkB;CAC/B,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO,EAAE,SAAS,KAAK;CAC9C,IAAI;EACF,MAAM,MAAM,aAAa,MAAM,OAAO;EACtC,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,OAAO,EACL,SAAS,OAAO,QAAQ,YAAY,YAAY,OAAO,UAAU,KACnE;CACF,QAAQ;EACN,OAAO,EAAE,SAAS,KAAK;CACzB;AACF;;AAGA,SAAS,kBAAkB,QAA6B;CACtD,MAAM,OAAO,kBAAkB;CAC/B,MAAM,MAAM,QAAQ,IAAI;CACxB,IAAI,CAAC,WAAW,GAAG,GAAG,UAAU,KAAK;EAAE,MAAM;EAAO,WAAW;CAAK,CAAC;CACrE,cAAc,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO;AACrE;;;;;;;AAUA,SAAgB,2BAA2B,MAA6C;CACtF,MAAM,UAAU,kBAAkB;CAClC,IAAI;CAEJ,IAAI,KAAK,QACP,WAAW;MACN,IAAI,KAAK,SACd,WAAW;MAGX,WAAW,CAAC,QAAQ;CAGtB,kBAAkB,EAAE,SAAS,SAAS,CAAC;CAEvC,MAAM,aAAa,WAAW,QAAQ;CACtC,MAAM,aAAa,WAAW,MAAM;CACpC,MAAM,eAAe,QAAQ,UAAU,OAAO;CAE9C,IAAI,KAAK,UAAU,KAAK,SACtB,OAAO,EACL,QAAQ,QAAQ,WAAW,SAAS,aAAa,OAAO,WAAW,IACrE;CAGF,OAAO,EACL,QAAQ,QAAQ,WAAW,UAAU,aAAa,KAAK,WAAW,GACpE;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade-CREWRNeC.mjs","names":[],"sources":["../src/commands/upgrade.ts"],"sourcesContent":["/**\n * /upgrade — 自助升级 Lynx 到最新版本。\n *\n * 支持两种模式:\n * - --check:检查依赖更新(运行 pnpm outdated)\n * - 默认:显示升级步骤,引导用户手动执行\n *\n * 由于升级涉及 git pull + pnpm install + pnpm build,\n * 直接在本地执行比通过模型指令更可靠。\n */\n\nimport { execSync } from \"node:child_process\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface UpgradeCommandArgs {\n /** 是否只检查更新而不执行升级。 */\n check?: boolean;\n /** 目标版本(暂未实现,预留)。 */\n version?: string;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /upgrade 命令。\n *\n * --check 模式运行 pnpm outdated 检查可用更新。\n * 默认模式显示手动升级步骤。\n */\nexport function handleUpgradeCommand(args: UpgradeCommandArgs): { output: string } {\n if (args.check) {\n return handleCheck();\n }\n\n const lines: string[] = [\n \"【Lynx 升级指南】\",\n \"\",\n \"请按以下步骤手动升级:\",\n \"\",\n \"1. 拉取最新代码\",\n \" git pull\",\n \"\",\n \"2. 安装依赖(如有更新)\",\n \" pnpm install --no-frozen-lockfile\",\n \"\",\n \"3. 重新构建\",\n \" pnpm build\",\n \"\",\n \"4. 验证升级\",\n \" node lynx.mjs --version\",\n \"\",\n \"提示:\",\n \"- 升级前建议先 git stash 暂存当前修改\",\n \"- 使用 lynx upgrade --check 可检查依赖是否有可用更新\",\n \"- 如果升级后出现问题,可 git reset --hard HEAD~1 回退\",\n ];\n\n return { output: lines.join(\"\\n\") };\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/**\n * 运行 pnpm outdated 检查过期的依赖。\n * 超时 30 秒,避免在网络慢时长时间阻塞。\n */\nfunction handleCheck(): { output: string } {\n try {\n const result = execSync(\"pnpm outdated\", {\n cwd: process.cwd(),\n encoding: \"utf-8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const output = result.trim();\n if (!output) {\n return { output: \"所有依赖均为最新版本。\" };\n }\n return { output: \"以下依赖有可用更新:\\n\\n\" + output };\n } catch (err) {\n // pnpm outdated 在没有过期包时返回非零退出码\n const exitCode = (err as { status?: number }).status;\n const stderr = (err as { stderr?: string }).stderr ?? \"\";\n\n if (exitCode === 1 && !stderr) {\n return { output: \"所有依赖均为最新版本。\" };\n }\n\n const message = err instanceof Error ? err.message : String(err);\n return {\n output:\n \"检查更新失败:\" + message + \"\\n\\n请确认当前目录是 Lynx 项目根目录,且 pnpm 已正确安装。\",\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"upgrade-CREWRNeC.mjs","names":[],"sources":["../src/commands/upgrade.ts"],"sourcesContent":["/**\n * /upgrade — 自助升级 Lynx 到最新版本。\n *\n * 支持两种模式:\n * - --check:检查依赖更新(运行 pnpm outdated)\n * - 默认:显示升级步骤,引导用户手动执行\n *\n * 由于升级涉及 git pull + pnpm install + pnpm build,\n * 直接在本地执行比通过模型指令更可靠。\n */\n\nimport { execSync } from \"node:child_process\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Arguments for /upgrade — check-only flag and optional target version for self-upgrading Lynx. */\nexport interface UpgradeCommandArgs {\n /** 是否只检查更新而不执行升级。 */\n check?: boolean;\n /** 目标版本(暂未实现,预留)。 */\n version?: string;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /upgrade 命令。\n *\n * --check 模式运行 pnpm outdated 检查可用更新。\n * 默认模式显示手动升级步骤。\n */\nexport function handleUpgradeCommand(args: UpgradeCommandArgs): { output: string } {\n if (args.check) {\n return handleCheck();\n }\n\n const lines: string[] = [\n \"【Lynx 升级指南】\",\n \"\",\n \"请按以下步骤手动升级:\",\n \"\",\n \"1. 拉取最新代码\",\n \" git pull\",\n \"\",\n \"2. 安装依赖(如有更新)\",\n \" pnpm install --no-frozen-lockfile\",\n \"\",\n \"3. 重新构建\",\n \" pnpm build\",\n \"\",\n \"4. 验证升级\",\n \" node lynx.mjs --version\",\n \"\",\n \"提示:\",\n \"- 升级前建议先 git stash 暂存当前修改\",\n \"- 使用 lynx upgrade --check 可检查依赖是否有可用更新\",\n \"- 如果升级后出现问题,可 git reset --hard HEAD~1 回退\",\n ];\n\n return { output: lines.join(\"\\n\") };\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/**\n * 运行 pnpm outdated 检查过期的依赖。\n * 超时 30 秒,避免在网络慢时长时间阻塞。\n */\nfunction handleCheck(): { output: string } {\n try {\n const result = execSync(\"pnpm outdated\", {\n cwd: process.cwd(),\n encoding: \"utf-8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const output = result.trim();\n if (!output) {\n return { output: \"所有依赖均为最新版本。\" };\n }\n return { output: \"以下依赖有可用更新:\\n\\n\" + output };\n } catch (err) {\n // pnpm outdated 在没有过期包时返回非零退出码\n const exitCode = (err as { status?: number }).status;\n const stderr = (err as { stderr?: string }).stderr ?? \"\";\n\n if (exitCode === 1 && !stderr) {\n return { output: \"所有依赖均为最新版本。\" };\n }\n\n const message = err instanceof Error ? err.message : String(err);\n return {\n output:\n \"检查更新失败:\" + message + \"\\n\\n请确认当前目录是 Lynx 项目根目录,且 pnpm 已正确安装。\",\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+BA,SAAgB,qBAAqB,MAA8C;CACjF,IAAI,KAAK,OACP,OAAO,YAAY;CA0BrB,OAAO,EAAE,QAAQ;EAtBf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAGmB,CAAC,CAAC,KAAK,IAAI,EAAE;AACpC;;;;;AAQA,SAAS,cAAkC;CACzC,IAAI;EAOF,MAAM,SANS,SAAS,iBAAiB;GACvC,KAAK,QAAQ,IAAI;GACjB,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAU;IAAQ;GAAM;EAClC,CACoB,CAAC,CAAC,KAAK;EAC3B,IAAI,CAAC,QACH,OAAO,EAAE,QAAQ,cAAc;EAEjC,OAAO,EAAE,QAAQ,mBAAmB,OAAO;CAC7C,SAAS,KAAK;EAEZ,MAAM,WAAY,IAA4B;EAC9C,MAAM,SAAU,IAA4B,UAAU;EAEtD,IAAI,aAAa,KAAK,CAAC,QACrB,OAAO,EAAE,QAAQ,cAAc;EAIjC,OAAO,EACL,QACE,aAHY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KAGrC,wCAC1B;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@24klynx/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "CLI entry point — bootstrap, command catalog, crestodian, doctor",
|
|
5
|
+
"bin": {
|
|
6
|
+
"lynx": "./bin/lynx.mjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"bin"
|
|
11
|
+
],
|
|
5
12
|
"type": "module",
|
|
6
13
|
"main": "./dist/index.mjs",
|
|
7
14
|
"types": "./dist/index.d.mts",
|
|
@@ -11,29 +18,22 @@
|
|
|
11
18
|
"types": "./dist/index.d.mts"
|
|
12
19
|
}
|
|
13
20
|
},
|
|
14
|
-
"
|
|
15
|
-
"
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
16
23
|
},
|
|
17
24
|
"dependencies": {
|
|
18
25
|
"commander": "^13.1.0",
|
|
19
26
|
"ink": "^6.8.0",
|
|
20
27
|
"react": "^19.2.0",
|
|
21
|
-
"@24klynx/agent": "0.1.
|
|
22
|
-
"@24klynx/core": "0.1.
|
|
23
|
-
"@24klynx/
|
|
24
|
-
"@24klynx/
|
|
25
|
-
"@24klynx/
|
|
26
|
-
"@24klynx/
|
|
27
|
-
"@24klynx/
|
|
28
|
-
"@24klynx/
|
|
29
|
-
"@24klynx/
|
|
30
|
-
},
|
|
31
|
-
"files": [
|
|
32
|
-
"dist",
|
|
33
|
-
"bin"
|
|
34
|
-
],
|
|
35
|
-
"publishConfig": {
|
|
36
|
-
"access": "public"
|
|
28
|
+
"@24klynx/agent": "0.1.5",
|
|
29
|
+
"@24klynx/core": "0.1.5",
|
|
30
|
+
"@24klynx/plugins": "0.1.5",
|
|
31
|
+
"@24klynx/llm": "0.1.5",
|
|
32
|
+
"@24klynx/channels": "0.1.5",
|
|
33
|
+
"@24klynx/session": "0.1.5",
|
|
34
|
+
"@24klynx/permissions": "0.1.5",
|
|
35
|
+
"@24klynx/tools": "0.1.5",
|
|
36
|
+
"@24klynx/tui": "0.1.5"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsdown --config-loader tsx",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-Braj-aBw.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 \"@24klynx/core\";\nimport type { LlmProvider } from \"@24klynx/llm\";\nimport { createDeepSeekProvider, createAnthropicProvider, createOpenAiProvider } from \"@24klynx/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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"}
|
package/dist/config-D9DbomLt.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"headless-launcher-CnSrw3bm.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 \"@24klynx/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-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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"}
|