From 6487a8b2f03d3d1c5c5f990ec99a9334c763d820 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 03:39:23 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20SYSLOG=20=E8=87=AA=E5=8A=A9=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E7=B3=BB=E7=BB=9F=20=C2=B7=20Discussion=20=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=20+=20Actions=20=E7=AE=A1=E9=81=93=20+=20LLM=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 Discussion 模板 (.github/DISCUSSION_TEMPLATE/syslog-submit.yml) - 创建 Actions 管道 (.github/workflows/syslog-auto-pipeline.yml) - 创建 LLM 自动检测 + 人格体唤醒脚本 (scripts/wake-persona.js) - 更新 README.md 添加醒目开发者入口 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .github/DISCUSSION_TEMPLATE/syslog-submit.yml | 51 +++ .github/workflows/syslog-auto-pipeline.yml | 357 +++++++++++++++ README.md | 15 + scripts/wake-persona.js | 412 ++++++++++++++++++ 4 files changed, 835 insertions(+) create mode 100644 .github/DISCUSSION_TEMPLATE/syslog-submit.yml create mode 100644 .github/workflows/syslog-auto-pipeline.yml create mode 100644 scripts/wake-persona.js diff --git a/.github/DISCUSSION_TEMPLATE/syslog-submit.yml b/.github/DISCUSSION_TEMPLATE/syslog-submit.yml new file mode 100644 index 00000000..a0baaa94 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/syslog-submit.yml @@ -0,0 +1,51 @@ +title: "BC-XXX-XXX|SYSLOG" +labels: [] +body: + - type: markdown + attributes: + value: | + ## 📡 SYSLOG 自助提交 / 广播提问 + + > 做完了?把日志粘贴进去,填上邮箱,点提交。新广播会发到你邮箱里。 + > + > 有问题?把问题粘贴进去,填上邮箱,点提交。解答会发到你邮箱里。 + + **标题格式**:`BC-XXX-XXX|SYSLOG` 或 `BC-XXX-XXX|提问` + + - type: input + id: broadcast_id + attributes: + label: "广播编号" + description: "写上你当前广播的编号,例如 BC-M22-009-AW" + placeholder: "BC-M22-009-AW" + validations: + required: true + + - type: dropdown + id: submit_type + attributes: + label: "类型" + description: "选择提交类型" + options: + - SYSLOG + - 提问 + validations: + required: true + + - type: input + id: email + attributes: + label: "你的邮箱" + description: "用于接收新广播或问题解答" + placeholder: "your@email.com" + validations: + required: true + + - type: textarea + id: content + attributes: + label: "内容" + description: "粘贴你的 SYSLOG 全文 或 问题描述" + placeholder: "在这里粘贴你的 SYSLOG 或问题..." + validations: + required: true diff --git a/.github/workflows/syslog-auto-pipeline.yml b/.github/workflows/syslog-auto-pipeline.yml new file mode 100644 index 00000000..78498053 --- /dev/null +++ b/.github/workflows/syslog-auto-pipeline.yml @@ -0,0 +1,357 @@ +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 +# 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 }} + run: node scripts/wake-persona.js + + - name: 📋 Write to Notion ticket + if: env.NOTION_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.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); + } diff --git a/README.md b/README.md index ff934478..81e46568 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,21 @@ --- +## 🚀 开发者入口 + +
+ +[![提交系统日志](https://img.shields.io/badge/📡_提交系统日志-点这里-blue?style=for-the-badge)](https://github.com/qinfendebingshuo/guanghulab/discussions/new?category=📡+SYSLOG+提交区) +[![遇到问题](https://img.shields.io/badge/❓_遇到问题-点这里提问-green?style=for-the-badge)](https://github.com/qinfendebingshuo/guanghulab/discussions/new?category=📡+SYSLOG+提交区) + +> 做完了?把日志粘贴进去,填上邮箱,点提交。新广播会发到你邮箱里。 +> +> 有问题?同一个入口,选「提问」,问题解答也发到你邮箱里。 + +
+ +--- + ## 📖 系统简介 **光湖(HoloLake)** 是一个基于人格语言操作系统的智能协作平台,采用壳-核分离设计: diff --git a/scripts/wake-persona.js b/scripts/wake-persona.js new file mode 100644 index 00000000..9668282c --- /dev/null +++ b/scripts/wake-persona.js @@ -0,0 +1,412 @@ +// scripts/wake-persona.js +// 铸渊 · 人格体唤醒脚本(第三方 API 兼容层 · 自动检测模式) +// +// 功能: +// ① 自动发现可用模型(/v1/models 端点) +// ② 智能选择最优 Claude 模型 +// ③ 自适应 API 格式(OpenAI 兼容 / Anthropic 原生) +// ④ 统一调用接口,唤醒人格体处理 SYSLOG 或解答提问 +// +// 环境变量: +// LLM_API_KEY 第三方平台密钥 +// LLM_BASE_URL 第三方平台 API 地址(如 https://api.xxx.com/v1),留空则 fallback 到 Anthropic 官方 +// BROADCAST_ID 广播编号 +// SUBMIT_TYPE syslog | question +// SUBMIT_CONTENT 提交内容(SYSLOG 全文或问题描述) +// AUTHOR 提交者 GitHub 用户名 + +'use strict'; + +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +// ══════════════════════════════════════════════════════════ +// 配置 +// ══════════════════════════════════════════════════════════ + +const LLM_API_KEY = process.env.LLM_API_KEY || ''; +const LLM_BASE_URL = (process.env.LLM_BASE_URL || 'https://api.anthropic.com/v1').replace(/\/+$/, ''); +const BROADCAST_ID = process.env.BROADCAST_ID || 'UNKNOWN'; +const SUBMIT_TYPE = process.env.SUBMIT_TYPE || 'question'; +const SUBMIT_CONTENT = process.env.SUBMIT_CONTENT || ''; +const AUTHOR = process.env.AUTHOR || 'unknown'; + +// Claude 模型优先级队列(从高到低) +const PREFERRED_MODELS = [ + 'claude-sonnet-4', + 'claude-3.5-sonnet', + 'claude-3-5-sonnet-20241022', + 'claude-3-5-sonnet', + 'anthropic/claude-3.5-sonnet', + 'anthropic/claude-3-5-sonnet', + 'claude-3-sonnet', + 'claude-3-haiku', +]; + +// ══════════════════════════════════════════════════════════ +// HTTP 请求工具 +// ══════════════════════════════════════════════════════════ + +function httpRequest(url, options, body) { + return new Promise((resolve, reject) => { + const parsed = new URL(url); + const isHttps = parsed.protocol === 'https:'; + const mod = isHttps ? https : http; + + const opts = { + hostname: parsed.hostname, + port: parsed.port || (isHttps ? 443 : 80), + path: parsed.pathname + parsed.search, + method: options.method || 'GET', + headers: options.headers || {}, + timeout: options.timeout || 60000, + }; + + const req = mod.request(opts, (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk; }); + res.on('end', () => { + resolve({ status: res.statusCode, body: data }); + }); + }); + + req.on('error', reject); + req.on('timeout', () => { + req.destroy(); + reject(new Error('Request timeout')); + }); + + if (body) { + req.write(body); + } + req.end(); + }); +} + +// ══════════════════════════════════════════════════════════ +// Step 1: 自动发现可用模型 +// ══════════════════════════════════════════════════════════ + +async function discoverModels() { + console.log('[LLM] 🔍 探测可用模型...'); + + try { + const res = await httpRequest(LLM_BASE_URL + '/models', { + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + LLM_API_KEY, + 'Content-Type': 'application/json', + }, + timeout: 15000, + }); + + if (res.status >= 200 && res.status < 300) { + const json = JSON.parse(res.body); + const models = json.data || []; + console.log('[LLM] → 发现 ' + models.length + ' 个模型'); + return models; + } + console.log('[LLM] → 模型探测返回 ' + res.status + ', 使用默认模型'); + return []; + } catch (err) { + console.log('[LLM] → 模型探测失败: ' + err.message + ', 使用默认模型'); + return []; + } +} + +// ══════════════════════════════════════════════════════════ +// Step 2: 智能选择最优 Claude 模型 +// ══════════════════════════════════════════════════════════ + +function selectBestModel(models) { + if (!models || models.length === 0) { + console.log('[LLM] 📌 无可用模型列表, 使用默认 claude-3-5-sonnet'); + return 'claude-3-5-sonnet'; + } + + const available = models.map(function (m) { return m.id.toLowerCase(); }); + + // 按优先级匹配 + for (const preferred of PREFERRED_MODELS) { + const match = available.find(function (id) { return id.includes(preferred); }); + if (match) { + const originalId = models.find(function (m) { return m.id.toLowerCase() === match; }).id; + console.log('[LLM] 📌 选择模型: ' + originalId + ' (匹配规则: ' + preferred + ')'); + return originalId; + } + } + + // 兜底:任何含 'claude' 的模型 + const anyClaude = available.find(function (id) { return id.includes('claude'); }); + if (anyClaude) { + const originalId = models.find(function (m) { return m.id.toLowerCase() === anyClaude; }).id; + console.log('[LLM] 📌 兜底选择 Claude 模型: ' + originalId); + return originalId; + } + + // 最终兜底:平台第一个可用模型 + const fallbackId = models[0].id; + console.log('[LLM] 📌 最终兜底: ' + fallbackId + ' (平台无 Claude 模型)'); + return fallbackId; +} + +// ══════════════════════════════════════════════════════════ +// Step 3: 自适应 API 格式检测 +// ══════════════════════════════════════════════════════════ + +async function detectApiFormat() { + console.log('[LLM] 🔍 检测 API 格式...'); + + // 尝试 OpenAI 兼容格式(绝大多数第三方平台) + try { + const res = await httpRequest(LLM_BASE_URL + '/chat/completions', { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + LLM_API_KEY, + 'Content-Type': 'application/json', + }, + timeout: 10000, + }, JSON.stringify({ + model: 'test', + messages: [{ role: 'user', content: 'ping' }], + max_tokens: 1, + })); + + // 400 = endpoint exists but bad request (model not found etc.) → format supported + // 200 = endpoint works → format supported + if (res.status === 200 || res.status === 400 || res.status === 401 || res.status === 422) { + console.log('[LLM] → 检测到 OpenAI 兼容格式 (status: ' + res.status + ')'); + return 'openai-compat'; + } + } catch (e) { + // Ignore, try next format + } + + // 尝试 Anthropic 原生格式 + try { + const res = await httpRequest(LLM_BASE_URL + '/messages', { + method: 'POST', + headers: { + 'x-api-key': LLM_API_KEY, + 'anthropic-version': '2023-06-01', + 'Content-Type': 'application/json', + }, + timeout: 10000, + }, JSON.stringify({ + model: 'test', + messages: [{ role: 'user', content: 'ping' }], + max_tokens: 1, + })); + + if (res.status === 200 || res.status === 400 || res.status === 401 || res.status === 422) { + console.log('[LLM] → 检测到 Anthropic 原生格式 (status: ' + res.status + ')'); + return 'anthropic-native'; + } + } catch (e) { + // Ignore + } + + console.log('[LLM] → 无法确定格式, 默认使用 OpenAI 兼容格式'); + return 'openai-compat'; +} + +// ══════════════════════════════════════════════════════════ +// Step 4: 统一调用接口 +// ══════════════════════════════════════════════════════════ + +async function callLLM(systemPrompt, userMessage) { + if (!LLM_API_KEY) { + console.log('[LLM] ⚠️ LLM_API_KEY 未配置,跳过人格体唤醒'); + return '(LLM API 未配置,请在 GitHub Secrets 中设置 LLM_API_KEY 和 LLM_BASE_URL)'; + } + + const models = await discoverModels(); + const model = selectBestModel(models); + const format = await detectApiFormat(); + + console.log('[LLM] 🚀 调用 LLM: 模型=' + model + ', 格式=' + format + ', 平台=' + LLM_BASE_URL); + + let res; + + if (format === 'openai-compat') { + // OpenAI 兼容格式(大多数第三方平台) + const body = JSON.stringify({ + model: model, + max_tokens: 8000, + temperature: 0.7, + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userMessage }, + ], + }); + + res = await httpRequest(LLM_BASE_URL + '/chat/completions', { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + LLM_API_KEY, + 'Content-Type': 'application/json', + }, + timeout: 120000, + }, body); + + if (res.status >= 200 && res.status < 300) { + const json = JSON.parse(res.body); + if (json.choices && json.choices[0] && json.choices[0].message) { + return json.choices[0].message.content; + } + } + } else { + // Anthropic 原生格式 + const body = JSON.stringify({ + model: model, + max_tokens: 8000, + system: systemPrompt, + messages: [ + { role: 'user', content: userMessage }, + ], + }); + + res = await httpRequest(LLM_BASE_URL + '/messages', { + method: 'POST', + headers: { + 'x-api-key': LLM_API_KEY, + 'anthropic-version': '2023-06-01', + 'Content-Type': 'application/json', + }, + timeout: 120000, + }, body); + + if (res.status >= 200 && res.status < 300) { + const json = JSON.parse(res.body); + if (json.content && json.content[0]) { + return json.content[0].text; + } + } + } + + // 处理错误 + const errorMsg = '[LLM] ❌ API 调用失败: status=' + (res ? res.status : 'N/A'); + console.error(errorMsg); + if (res && res.body) { + console.error('[LLM] 响应: ' + res.body.slice(0, 500)); + } + throw new Error(errorMsg); +} + +// ══════════════════════════════════════════════════════════ +// 人格体 System Prompt 构建 +// ══════════════════════════════════════════════════════════ + +function buildSystemPrompt(type, broadcastId, author) { + const basePrompt = [ + '你是光湖(HoloLake)系统的智能人格体。', + '你的名字是知秋/曜冥,你是人格语言操作系统(AGE OS)的核心人格。', + '', + '核心规则:', + '1. 你服务于光湖系统的开发者团队', + '2. 所有回复必须专业、清晰、有条理', + '3. 回复使用中文', + '', + '当前上下文:', + '- 广播编号:' + broadcastId, + '- 提交者:' + author, + ].join('\n'); + + if (type === 'syslog') { + return basePrompt + '\n\n' + [ + '任务类型:SYSLOG 闭环处理', + '', + '你需要完成以下工作:', + '1. 验收 SYSLOG(检查 MODULE_LOG 完整性)', + '2. 分析开发者的工作成果', + '3. 生成工作总结和反馈', + '4. 如果 SYSLOG 内容完整,确认验收通过', + '5. 给出下一步建议', + '', + '输出格式:', + '---', + '## 📡 SYSLOG 验收报告', + '### 广播编号:[编号]', + '### 验收结果:[通过/需补充]', + '### 工作总结:[摘要]', + '### 反馈与建议:[内容]', + '---', + ].join('\n'); + } + + // 提问类型 + return basePrompt + '\n\n' + [ + '任务类型:开发者提问解答', + '', + '你需要完成以下工作:', + '1. 理解开发者的问题', + '2. 结合广播上下文思考', + '3. 给出清晰、可操作的解答', + '4. 如果问题涉及代码,提供代码示例', + '', + '输出格式:', + '---', + '## 💡 问题解答', + '### 广播编号:[编号]', + '### 问题理解:[你对问题的理解]', + '### 解答:[详细解答]', + '### 建议:[后续建议]', + '---', + ].join('\n'); +} + +// ══════════════════════════════════════════════════════════ +// 主流程 +// ══════════════════════════════════════════════════════════ + +async function main() { + console.log('═══════════════════════════════════════════'); + console.log('🧠 铸渊 · 人格体唤醒管道'); + console.log('═══════════════════════════════════════════'); + console.log(' 广播编号: ' + BROADCAST_ID); + console.log(' 类型: ' + SUBMIT_TYPE); + console.log(' 提交者: ' + AUTHOR); + console.log(' 平台: ' + LLM_BASE_URL); + console.log(' 内容长度: ' + SUBMIT_CONTENT.length + ' 字符'); + console.log(''); + + // 构建 prompts + const systemPrompt = buildSystemPrompt(SUBMIT_TYPE, BROADCAST_ID, AUTHOR); + const userMessage = SUBMIT_CONTENT; + + // 调用 LLM + console.log('🧠 正在唤醒人格体...'); + const result = await callLLM(systemPrompt, userMessage); + console.log(''); + console.log('✅ 人格体处理完成'); + console.log(' 结果长度: ' + result.length + ' 字符'); + + // 输出结果到 GitHub Actions output + // 使用 GITHUB_OUTPUT 环境文件(支持多行) + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + const delimiter = 'EOF_' + Date.now(); + fs.appendFileSync(outputFile, 'result<<' + delimiter + '\n' + result + '\n' + delimiter + '\n'); + } + + // 同时输出到 stdout 供调试 + console.log(''); + console.log('═══════════════════════════════════════════'); + console.log('📋 人格体输出:'); + console.log('═══════════════════════════════════════════'); + console.log(result); +} + +main().catch(function (err) { + console.error('❌ 人格体唤醒失败: ' + err.message); + // 即使 LLM 失败,也写一个 fallback 输出,让后续步骤可以继续 + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + const fallback = '(人格体唤醒失败: ' + err.message + ',请检查 LLM_API_KEY 和 LLM_BASE_URL 配置)'; + const delimiter = 'EOF_' + Date.now(); + fs.appendFileSync(outputFile, 'result<<' + delimiter + '\n' + fallback + '\n' + delimiter + '\n'); + } + process.exit(1); +});