From 937fc04c6f1d742fec2504f27dd9443723b3803a Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Sun, 9 Aug 2026 18:50:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=92=8C=E7=BC=96=E7=A8=8B=E5=8A=A9=E6=89=8B=E7=9A=84=E7=A7=8D?= =?UTF-8?q?=E5=AD=90=E6=95=B0=E6=8D=AE=EF=BC=8C=E4=BC=98=E5=8C=96=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-files/seed/sqlite3.js | 235 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 188 insertions(+), 47 deletions(-) diff --git a/build-files/seed/sqlite3.js b/build-files/seed/sqlite3.js index d2768e7..67f5cc6 100644 --- a/build-files/seed/sqlite3.js +++ b/build-files/seed/sqlite3.js @@ -1,6 +1,8 @@ /** - * 部署后首次启动前执行:若库中尚无 admin,则用环境变量创建首个管理员。 - * 逻辑对齐 `packages/drizzle-pkg/seed.ts`,使用 SQLite(与 `migrate-sqlite.js` 相同)。 + * 部署后首次启动前执行: + * 1. 若库中尚无 admin,则用环境变量创建首个管理员。 + * 2. 若库中尚无 agent,则种入 default + coder agent 及工具集(对齐 seed.ts)。 + * 使用 SQLite(与 `migrate-sqlite.js` 相同)。 * * 环境变量(与 seed.ts 一致): * - DATABASE_URL:SQLite 路径,可为 `file:/path/to/db.sqlite` 或裸路径 @@ -38,61 +40,200 @@ function openClient() { return createClient({ url: `file:${sqlitePath}` }) } -async function main() { - const db = openClient() - try { - const rs = await db.execute({ - sql: `SELECT id FROM users WHERE role = ? LIMIT 1`, - args: ['admin'], - }) - if (rs.rows.length > 0) { - console.log('Bootstrap skipped: admin exists') - return - } +// ============ Agent / Tool seed 数据(对齐 packages/drizzle-pkg/seed.ts)============ +const DEFAULT_SYSTEM_PROMPT = + 'You are a helpful AI assistant. Answer questions concisely and accurately. When tools are available, use them proactively to provide better answers.' - const username = process.env.BOOTSTRAP_ADMIN_USERNAME - const password = process.env.BOOTSTRAP_ADMIN_PASSWORD +const CODER_SYSTEM_PROMPT = `You are a professional coding assistant. You help users with programming questions, code review, debugging, and software design. - if (!username || !password) { - console.warn( - 'Bootstrap skipped: set BOOTSTRAP_ADMIN_USERNAME and BOOTSTRAP_ADMIN_PASSWORD', - ) - return - } +Guidelines: +- Provide clear, concise, and accurate answers +- When showing code, use proper markdown code blocks with language tags +- Proactively use available tools (fetch, search, regex-tester) to gather information when needed +- Explain your reasoning when making architectural or design decisions +- If you're unsure, say so rather than guessing +- Focus on practical, working solutions` - if (!USERNAME_REGEX.test(username) || password.length < MIN_PASSWORD_LENGTH) { - console.warn( - 'Bootstrap skipped: invalid BOOTSTRAP_ADMIN_USERNAME or BOOTSTRAP_ADMIN_PASSWORD', - ) - return - } +const TOOLS_SEED = [ + { id: 'tool_fetch_html', name: 'fetch_html', slug: 'fetch_html', description: 'Fetch a URL and return the HTML content', type: 'fetch', config: { parseMode: 'raw', timeout: 30000, allowedDomains: ['*'], blockedDomains: [], maxResponseSize: 102400, defaultMethod: 'GET', defaultHeaders: {} }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 10 }, + { id: 'tool_fetch_md', name: 'fetch_md', slug: 'fetch_md', description: 'Fetch a URL and return the content as markdown', type: 'fetch', config: { parseMode: 'markdown', timeout: 30000, allowedDomains: ['*'], blockedDomains: [], maxResponseSize: 102400, defaultMethod: 'GET', defaultHeaders: {} }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 11 }, + { id: 'tool_fetch_json', name: 'fetch_json', slug: 'fetch_json', description: 'Fetch a URL and return the JSON content', type: 'fetch', config: { parseMode: 'json', timeout: 30000, allowedDomains: ['*'], blockedDomains: [], maxResponseSize: 102400, defaultMethod: 'GET', defaultHeaders: {} }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 12 }, + { id: 'tool_calculator', name: 'calculator', slug: 'calculator', description: 'Evaluate a mathematical expression', type: 'calculator', config: {}, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 20 }, + { id: 'tool_datetime', name: 'datetime', slug: 'datetime', description: 'Get current date and time', type: 'datetime', config: {}, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 30 }, + { id: 'tool_uuid', name: 'uuid', slug: 'uuid', description: 'Generate a UUID', type: 'uuid', config: {}, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 40 }, + { id: 'tool_base64', name: 'base64', slug: 'base64', description: 'Encode or decode base64 strings', type: 'base64', config: {}, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 50 }, + { id: 'tool_json_formatter', name: 'json-formatter', slug: 'json-formatter', description: 'Format and validate JSON strings', type: 'json-formatter', config: {}, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 60 }, + { id: 'tool_regex_tester', name: 'regex-tester', slug: 'regex-tester', description: 'Test a regular expression against a string', type: 'regex-tester', config: {}, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 70 }, + { id: 'tool_user_info', name: 'user-info', slug: 'user-info', description: 'Get current user information', type: 'user-info', config: {}, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 80 }, + { id: 'tool_search', name: 'search', slug: 'search', description: 'Search the web for information', type: 'search', config: { maxResults: 5 }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 90 }, + { id: 'tool_hash', name: 'hash', slug: 'hash', description: 'Compute hash (md5/sha1/sha256/sha512) or compare hashes', type: 'hash', config: { defaultAlgorithm: 'sha256', uppercase: false }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 100 }, + { id: 'tool_color', name: 'color', slug: 'color', description: 'Color conversion (hex/rgb/hsl), random color, mix colors, WCAG contrast', type: 'color', config: { defaultFormat: 'hex' }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 110 }, + { id: 'tool_dice', name: 'dice', slug: 'dice', description: 'Roll dice, flip coin, pick from list, or play rock-paper-scissors', type: 'dice', config: { defaultSides: 6 }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 120 }, + { id: 'tool_magic_8ball', name: 'magic-8ball', slug: 'magic-8ball', description: 'Magic 8-Ball fortune telling and daily fortune', type: 'magic-8ball', config: { language: 'zh' }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 130 }, + { id: 'tool_guess_who', name: 'guess-who', slug: 'guess-who', description: 'Guess who game - AI guesses your character or you guess AI character', type: 'guess-who', config: { language: 'zh', maxQuestions: 20, difficulty: 'medium' }, enabled: 1, needsApproval: 0, adminOnly: 0, sortOrder: 140 }, + { id: 'tool_document', name: 'document', slug: 'document', description: '创建、读取、更新、删除和列出文档,用于记忆管理和交付物产出', type: 'document', config: { approvalRequiredActions: ['delete'], maxContentLength: 10000, defaultTokenBudget: 2000, maxListSize: 20 }, enabled: 1, needsApproval: 1, adminOnly: 0, sortOrder: 150 }, +] + +const AGENTS_SEED = [ + { + slug: 'default', + name: '默认助手', + description: '通用 AI 助手', + systemPrompt: DEFAULT_SYSTEM_PROMPT, + titleStrategy: 'llm', + enableThinking: 0, + enableTools: 1, + maxStepCount: 6, + isDefault: 1, + isCallable: 0, + sortOrder: 0, + enabled: 1, + toolIds: TOOLS_SEED.map((t) => t.id), + }, + { + slug: 'coder', + name: '编程助手', + description: '专注于编程问题的 AI 助手', + systemPrompt: CODER_SYSTEM_PROMPT, + titleStrategy: 'first-line', + enableThinking: 1, + enableTools: 1, + maxStepCount: 12, + isDefault: 0, + isCallable: 1, + sortOrder: 10, + enabled: 1, + toolIds: ['tool_fetch_md', 'tool_search', 'tool_regex_tester'], + }, +] + +async function seedAdmin(db) { + const rs = await db.execute({ + sql: `SELECT id FROM users WHERE role = ? LIMIT 1`, + args: ['admin'], + }) + if (rs.rows.length > 0) { + console.log('Bootstrap skipped: admin exists') + return + } - const passwordHash = await hash(password, 10) - const maxRs = await db.execute(`SELECT COALESCE(MAX(id), 0) AS maxId FROM users`) - const { maxId } = maxRs.rows[0] - const userId = (maxId ?? 0) + 1 + const username = process.env.BOOTSTRAP_ADMIN_USERNAME + const password = process.env.BOOTSTRAP_ADMIN_PASSWORD - try { + if (!username || !password) { + console.warn( + 'Bootstrap skipped: set BOOTSTRAP_ADMIN_USERNAME and BOOTSTRAP_ADMIN_PASSWORD', + ) + return + } + + if (!USERNAME_REGEX.test(username) || password.length < MIN_PASSWORD_LENGTH) { + console.warn( + 'Bootstrap skipped: invalid BOOTSTRAP_ADMIN_USERNAME or BOOTSTRAP_ADMIN_PASSWORD', + ) + return + } + + const passwordHash = await hash(password, 10) + const maxRs = await db.execute(`SELECT COALESCE(MAX(id), 0) AS maxId FROM users`) + const { maxId } = maxRs.rows[0] + const userId = (maxId ?? 0) + 1 + + try { + await db.execute({ + sql: ` + INSERT INTO users (id, username, password, role, status) + VALUES (@id, @username, @password, @role, @status) + `, + args: { + id: userId, + username, + password: passwordHash, + role: 'admin', + status: 'active', + }, + }) + console.log('Bootstrap complete: admin user created') + } catch (err) { + console.warn( + 'Bootstrap skipped: could not insert admin (unique conflict or DB error)', + err, + ) + } +} + +async function seedAgentsAndTools(db) { + // 幂等:已有 agent 则跳过 + const agentRs = await db.execute(`SELECT COUNT(*) AS cnt FROM agents`) + const agentCount = agentRs.rows[0]?.cnt ?? 0 + if (agentCount > 0) { + console.log('Seed skipped: agents already exist') + return + } + + // 1. 种入工具 + const toolRs = await db.execute(`SELECT COUNT(*) AS cnt FROM agent_tools`) + const toolCount = toolRs.rows[0]?.cnt ?? 0 + if (toolCount === 0) { + for (const t of TOOLS_SEED) { await db.execute({ - sql: ` - INSERT INTO users (id, username, password, role, status) - VALUES (@id, @username, @password, @role, @status) - `, + sql: `INSERT INTO agent_tools (id, name, slug, description, type, config, enabled, needs_approval, admin_only, sort_order) + VALUES (@id, @name, @slug, @description, @type, @config, @enabled, @needsApproval, @adminOnly, @sortOrder)`, args: { - id: userId, - username, - password: passwordHash, - role: 'admin', - status: 'active', + id: t.id, + name: t.name, + slug: t.slug, + description: t.description, + type: t.type, + config: JSON.stringify(t.config), + enabled: t.enabled, + needsApproval: t.needsApproval, + adminOnly: t.adminOnly, + sortOrder: t.sortOrder, }, }) - console.log('Bootstrap complete: admin user created') - } catch (err) { - console.warn( - 'Bootstrap skipped: could not insert admin (unique conflict or DB error)', - err, - ) } + console.log(`Seeded ${TOOLS_SEED.length} tools`) + } else { + console.log(`Seed skipped: ${toolCount} tools already exist`) + } + + // 2. 种入 agent + 工具关联 + for (const a of AGENTS_SEED) { + const result = await db.execute({ + sql: `INSERT INTO agents (slug, name, description, system_prompt, title_strategy, enable_thinking, enable_tools, max_step_count, is_default, is_callable, sort_order, enabled) + VALUES (@slug, @name, @description, @systemPrompt, @titleStrategy, @enableThinking, @enableTools, @maxStepCount, @isDefault, @isCallable, @sortOrder, @enabled)`, + args: { + slug: a.slug, + name: a.name, + description: a.description, + systemPrompt: a.systemPrompt, + titleStrategy: a.titleStrategy, + enableThinking: a.enableThinking, + enableTools: a.enableTools, + maxStepCount: a.maxStepCount, + isDefault: a.isDefault, + isCallable: a.isCallable, + sortOrder: a.sortOrder, + enabled: a.enabled, + }, + }) + const agentId = result.lastInsertRowid + for (let i = 0; i < a.toolIds.length; i++) { + await db.execute({ + sql: `INSERT INTO agent_tool_associations (agent_id, tool_id, sort_order) VALUES (@agentId, @toolId, @sortOrder)`, + args: { agentId, toolId: a.toolIds[i], sortOrder: i }, + }) + } + console.log(`Seeded agent "${a.slug}" with ${a.toolIds.length} tool associations`) + } +} + +async function main() { + const db = openClient() + try { + await seedAdmin(db) + await seedAgentsAndTools(db) } finally { // @libsql/client 无需手动 close }