import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { DatabaseWorkspaceService } from "./state/workspace"; import { DEFAULT_BASE } from "./state/state-store"; import { readToggle } from "./state/extension-toggle"; import { registerDbCommand } from "./commands/db"; import { applyWorkspaceStatus } from "./commands/switch"; import { registerRenderers } from "./commands/renderers"; import { registerDbTools, applyInitialToolSet } from "./tools/db-tools"; import { createDbStatusNotifier } from "./commands/llm-context"; import { createSessionAutoApprove } from "./state/mutation-approval"; import { normalizeScopeKey } from "./state/workspace-store"; const baseDir = dirname(fileURLToPath(import.meta.url)); export default function (pi: ExtensionAPI) { // 开关:工厂执行时同步读取(/db on|off 触发 ctx.reload() 后工厂重跑会重新 // 读取)。禁用时不注册任何 LLM 资源(tools / skills / renderers),只保留 // 精简版 /db 命令作为重新启用入口——见 docs/extension-toggle.md。 const enabled = readToggle(DEFAULT_BASE); // 懒初始化:扩展工厂可能运行在从不启动会话的调用中 // (print 模式、--list-models 等)。将打开 SQLite 和读取 // connections.yaml 推迟到第一次命令/工具调用或 session_start。 // // 项目级隔离:首次创建时用会话 cwd 定持久化键,选择不再跨项目携带 // (见 docs/workspace-scope-and-status-noise.md)。 let workspace: DatabaseWorkspaceService | null = null; const getWorkspace = (scopeKey?: string): DatabaseWorkspaceService => { workspace ??= new DatabaseWorkspaceService(undefined, undefined, { scopeKey: scopeKey ?? normalizeScopeKey(process.cwd()), }); return workspace; }; // 会话级注入器:同一状态每会话只告知模型一次;未选择时不注入。 const statusNotifier = createDbStatusNotifier(pi, getWorkspace); // 会话级"变更免确认"开关:纯内存、默认关闭,命令面与工具层共享同一实例。 const autoApprove = createSessionAutoApprove(); if (enabled) { // 自定义消息渲染器(紧凑查询结果、纯文本面板) registerRenderers(pi); // 注册 LLM 工具:常驻(db_query, db_tables, db_mutate)+ db_tools // loader(按需启用 db_discover, db_list_relations, db_relation—— // 见 tools/db-tool-catalog.ts)。 registerDbTools(pi, getWorkspace, autoApprove); } // 注册 /db 命令(禁用态为精简版,仅响应 on) registerDbCommand(pi, getWorkspace, enabled, DEFAULT_BASE, statusNotifier, autoApprove); // 注册内置 skills,使其随扩展被发现。禁用时返回空——skill 以 XML 块进 // 系统提示词,连带禁用以保持零上下文。 // skills/ 位于扩展目录内,包含在 npm 包中。 pi.on("resources_discover", () => { return { skillPaths: enabled ? [join(baseDir, "skills")] : [] }; }); // 会话开始时恢复状态栏 pi.on("session_start", (_event, ctx) => { // 禁用:不初始化 workspace、不恢复状态栏、不发消息。 if (!enabled) return; // 将工具集收窄为常驻工具 + loader。懒工具 // (db_discover, db_list_relations, db_relation)经 db_tools loader // 按需启用,保持 system prompt 精简。 applyInitialToolSet(pi); // 会话边界复位免确认开关(工厂重跑已复位,这里保证不变量)。 autoApprove.reset(); // 用会话 cwd 定键(/resume 可能进入另一个项目目录,进程 cwd 不一定一致)。 const ws = getWorkspace(normalizeScopeKey(ctx.cwd)); // 恢复状态栏与 widget;复位后免确认后缀必然为空(默认关闭零噪音)。 applyWorkspaceStatus(ctx, ws, autoApprove); // 告知 LLM 当前激活的数据库,避免它通过一次失败的调用去发现。 // 未选择数据库时不注入任何内容。 statusNotifier.notifyIfChanged(); }); // 关闭时清理 pi.on("session_shutdown", () => { workspace?.destroy(); workspace = null; }); }