name: SYSLOG Auto Pipeline # 📡 SYSLOG 自助提交系统 · 全自动闭环 # # 开发者在 GitHub Discussion 提交 SYSLOG 或提问 # → Actions 自动解析 → 调用 LLM API 唤醒人格体 # → 写入 Notion 工单 → 发邮件给开发者 → Discussion 回复 # # 依赖 Secrets: # LLM_API_KEY 第三方 LLM 平台密钥(必须) # LLM_BASE_URL 第三方 LLM 平台 API 地址(必须,如 https://api.xxx.com/v1) # NOTION_API_TOKEN Notion API token # NOTION_TICKET_DB_ID 霜砚工单数据库 ID # CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 ID(v4.0 协议动态注入) # PORTRAIT_DB_ID 开发者动态画像库数据库 ID(v4.0 协议动态注入) # FINGERPRINT_DB_ID 模块指纹注册表数据库 ID(v4.0 协议动态注入) # SMTP_USER QQ 邮箱地址 # SMTP_PASS QQ 邮箱 SMTP 授权码 on: discussion: types: [created] jobs: process: name: 📡 处理 SYSLOG 提交 / 广播提问 runs-on: ubuntu-latest if: contains(github.event.discussion.category.name, 'SYSLOG') permissions: contents: write discussions: write steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci --ignore-scripts - name: 🔍 Parse submission id: parse uses: actions/github-script@v7 with: script: | const body = context.payload.discussion.body || ''; const title = context.payload.discussion.title || ''; // Parse structured fields from discussion body function extractField(text, label) { // Match form field format: ### Label\n\nValue const regex = new RegExp('###\\s*' + label + '\\s*\\n+([^\\n#]+)', 'i'); const match = text.match(regex); return match ? match[1].trim() : ''; } const broadcastId = extractField(body, '广播编号') || ''; const submitType = extractField(body, '类型') || ''; const email = extractField(body, '你的邮箱') || ''; // Extract content (everything after "### 内容") const contentMatch = body.match(/###\s*内容\s*\n+([\s\S]*?)$/i); const content = contentMatch ? contentMatch[1].trim() : ''; // Determine type from title or body let type = 'question'; // default if (submitType.includes('SYSLOG') || title.includes('SYSLOG')) { type = 'syslog'; } else if (submitType.includes('提问') || title.includes('提问')) { type = 'question'; } // Validate if (!broadcastId) { core.setFailed('❌ 缺少广播编号,请在提交时填写广播编号'); return; } if (!email) { core.setFailed('❌ 缺少邮箱,请在提交时填写你的邮箱'); return; } if (!content) { core.setFailed('❌ 缺少内容,请粘贴你的 SYSLOG 或问题'); return; } core.setOutput('broadcast_id', broadcastId); core.setOutput('type', type); core.setOutput('email', email); core.setOutput('content', content); core.setOutput('discussion_number', context.payload.discussion.number); core.setOutput('author', context.payload.discussion.user.login); console.log(`📡 解析完成: 广播=${broadcastId}, 类型=${type}, 邮箱=${email}`); - name: 🧠 Auto-detect and wake up persona id: persona env: LLM_API_KEY: ${{ secrets.LLM_API_KEY }} LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }} BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }} SUBMIT_TYPE: ${{ steps.parse.outputs.type }} SUBMIT_CONTENT: ${{ steps.parse.outputs.content }} AUTHOR: ${{ steps.parse.outputs.author }} NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }} PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }} FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }} run: node scripts/wake-persona.js - name: 📋 Write to Notion ticket if: ${{ secrets.NOTION_API_TOKEN }} env: NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }} BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }} SUBMIT_TYPE: ${{ steps.parse.outputs.type }} PERSONA_RESULT: ${{ steps.persona.outputs.result }} run: | node -e " const https = require('https'); const token = process.env.NOTION_TOKEN; const dbId = process.env.NOTION_TICKET_DB_ID; const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN'; const type = process.env.SUBMIT_TYPE || 'syslog'; const result = process.env.PERSONA_RESULT || '(no result)'; if (!token || !dbId) { console.log('⚠️ Notion credentials not configured, skipping ticket creation'); process.exit(0); } const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答'; const title = '[自动] ' + broadcastId + ' · ' + typeLabel; const body = JSON.stringify({ parent: { database_id: dbId }, properties: { '标题': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] }, '操作类型': { select: { name: '其他' } }, '提交者': { rich_text: [{ type: 'text', text: { content: '自动管道' } }] }, '状态': { select: { name: '已完成' } }, '优先级': { select: { name: 'P1' } } }, children: [{ object: 'block', type: 'paragraph', paragraph: { rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }] } }] }); const opts = { hostname: 'api.notion.com', port: 443, path: '/v1/pages', method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json', 'Notion-Version': '2022-06-28', 'Content-Length': Buffer.byteLength(body) } }; const req = https.request(opts, (res) => { let data = ''; res.on('data', (c) => data += c); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { console.log('✅ Notion 工单已创建'); } else { console.log('⚠️ Notion 工单创建失败: ' + res.statusCode + ' ' + data); } }); }); req.on('error', (e) => console.log('⚠️ Notion 请求失败: ' + e.message)); req.write(body); req.end(); " - name: 📧 Send email to developer env: SMTP_USER: ${{ secrets.SMTP_USER }} SMTP_PASS: ${{ secrets.SMTP_PASS }} EMAIL_TO: ${{ steps.parse.outputs.email }} BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }} SUBMIT_TYPE: ${{ steps.parse.outputs.type }} PERSONA_RESULT: ${{ steps.persona.outputs.result }} run: | node -e " const nodemailer = require('nodemailer'); const user = process.env.SMTP_USER || ''; const pass = process.env.SMTP_PASS || ''; const to = process.env.EMAIL_TO || ''; const broadcastId = process.env.BROADCAST_ID || ''; const type = process.env.SUBMIT_TYPE || 'syslog'; const result = process.env.PERSONA_RESULT || '(处理中,请稍后)'; if (!user || !pass) { console.log('⚠️ SMTP not configured, skipping email'); process.exit(0); } if (!to) { console.log('⚠️ No recipient email, skipping'); process.exit(0); } const subjectText = type === 'syslog' ? '[光湖系统] ' + broadcastId + ' · 新广播已生成' : '[光湖系统] ' + broadcastId + ' · 问题已解答'; const resultHtml = result .replace(/&/g, '&') .replace(//g, '>') .replace(/\n/g, '
'); const html = [ '
', '
', '

🌊 光湖系统 · 自动通知

', '

HoloLake · 人格语言操作系统

', '
', '
', '

📡 ' + broadcastId + '

', '
' + resultHtml + '
', '
', '
', '↩ 回到仓库继续开发', '

🌀 铸渊 · 代码守护人格体 · 自动发送

', '
', '
' ].join('\n'); const transporter = nodemailer.createTransport({ host: 'smtp.qq.com', port: 465, secure: true, auth: { user, pass } }); transporter.sendMail({ from: '\"光湖系统\" <' + user + '>', to: to, subject: subjectText, html: html }).then((info) => { console.log('✅ 邮件已发送: ' + info.messageId); }).catch((err) => { console.log('⚠️ 邮件发送失败: ' + err.message); }); " - name: 💬 Reply to Discussion uses: actions/github-script@v7 with: script: | const number = ${{ steps.parse.outputs.discussion_number }}; const type = '${{ steps.parse.outputs.type }}'; const broadcastId = '${{ steps.parse.outputs.broadcast_id }}'; const email = '${{ steps.parse.outputs.email }}'; const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答'; const maskedEmail = email.length > 4 ? email.replace(/(.{2})(.*)(@.*)/, '$1***$3') : '***'; const body = [ '✅ **已处理** · ' + typeLabel, '', '| 项目 | 内容 |', '|------|------|', '| 📡 广播编号 | `' + broadcastId + '` |', '| 📧 结果发送至 | `' + maskedEmail + '` |', '| 🤖 处理人格体 | 铸渊 |', '| ⏰ 处理时间 | ' + new Date().toISOString() + ' |', '', '> 结果已发送到你的邮箱,请查收。', '>', '> 如未收到,请检查垃圾箱或重新提交。' ].join('\n'); // Use GraphQL to add discussion comment const query = ` mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: { discussionId: $discussionId, body: $body }) { comment { id } } } `; try { // Get the discussion node ID const { repository } = await github.graphql(` query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { discussion(number: $number) { id } } } `, { owner: context.repo.owner, repo: context.repo.repo, number: number }); await github.graphql(query, { discussionId: repository.discussion.id, body: body }); console.log('✅ Discussion 回复已发送'); } catch (err) { console.log('⚠️ Discussion 回复失败: ' + err.message); } - name: 🔴 失败告警 if: failure() uses: actions/github-script@v7 with: script: | const number = context.payload.discussion?.number; if (!number) return; try { const { repository } = await github.graphql(` query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { discussion(number: $number) { id } } } `, { owner: context.repo.owner, repo: context.repo.repo, number: number }); await github.graphql(` mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: { discussionId: $discussionId, body: $body }) { comment { id } } } `, { discussionId: repository.discussion.id, body: '❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 错误详情请查看 [Actions 日志](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions)。' }); } catch (err) { console.log('⚠️ 失败告警回复失败: ' + err.message); }