diff --git a/.github/workflows/notion-callback-pipeline.yml b/.github/workflows/notion-callback-pipeline.yml new file mode 100644 index 00000000..463d7ced --- /dev/null +++ b/.github/workflows/notion-callback-pipeline.yml @@ -0,0 +1,317 @@ +name: Notion Callback Pipeline +# ๐Ÿ“ฅ Notion โ†’ GitHub ๅ›žไผ ้—ญ็Žฏ็ฎก้“ +# +# ๅฝ“ Notion ไพงๆ ธๅฟƒๅคง่„‘ไบบๆ ผไฝ“ๅค„็†ๅฎŒ SYSLOG ๅทฅๅ•ๅŽ๏ผŒ +# ้€š่ฟ‡ repository_dispatch ๅ›žไผ ๅค„็†็ป“ๆžœๅˆฐ GitHubใ€‚ +# ้“ธๆธŠ Agent ๆŽฅๆ”ถๅ›žไผ ๏ผŒ้‡ๆ–ฐๅ”ค้†’ๆ ธๅฟƒๅคง่„‘่ฎค็Ÿฅ่ฟ›่กŒไบŒๆฌกๆ ธ้ชŒ๏ผŒ +# ็กฎ่ฎคๆ–ฐๅนฟๆ’ญ็”Ÿๆˆๆ— ่ฏฏๅŽ๏ผŒ่‡ชๅŠจๅ‘้€้‚ฎไปถ้€š็Ÿฅๅผ€ๅ‘่€…ใ€‚ +# +# ่งฆๅ‘ๆ–นๅผ๏ผš +# Notion ไพง Agent ่ฐƒ็”จ GitHub API ๅ‘้€ repository_dispatch ไบ‹ไปถ +# event_type: notion-syslog-callback +# client_payload: { +# broadcast_id: "BC-XXX-XXX-XX", +# dev_email: "developer@email.com", +# notion_result: "Notion ไพงๅค„็†็ป“ๆžœๆ–‡ๆœฌ", +# ticket_id: "Notion ๅทฅๅ• ID", +# status: "approved" | "needs_revision" +# } +# +# ้—ญ็Žฏๆต็จ‹๏ผš +# โ‘  Notion ไพงๅค„็†ๅฎŒๆˆ โ†’ repository_dispatch ๅ›žไผ  +# โ‘ก ้“ธๆธŠ Agent ๆŽฅๆ”ถๅนถ่งฃๆžๅ›žไผ ๆ•ฐๆฎ +# โ‘ข ้‡ๆ–ฐๅ”ค้†’้“ธๆธŠๆ ธๅฟƒๅคง่„‘่ฎค็Ÿฅ๏ผˆไบŒๆฌกๆ ธ้ชŒ๏ผ‰ +# โ‘ฃ ๆ ธๅฟƒๅคง่„‘ๆ ธๅฏนๅนฟๆ’ญ็”Ÿๆˆๆ— ่ฏฏ +# โ‘ค ้“ธๆธŠ Agent ๅ‘้€้‚ฎไปถ้€š็Ÿฅๅผ€ๅ‘่€… +# โ‘ฅ ้—ญ็Žฏ็ป“ๆŸ +# +# ไพ่ต– Secrets๏ผš +# LLM_API_KEY ็ฌฌไธ‰ๆ–น LLM ๅนณๅฐๅฏ†้’ฅ +# LLM_BASE_URL ็ฌฌไธ‰ๆ–น LLM ๅนณๅฐ API ๅœฐๅ€ +# NOTION_API_TOKEN Notion API token +# CORE_BRAIN_PAGE_ID ๆ›œๅ†ฅๆ ธๅฟƒๅคง่„‘ v4.0 ้กต้ข ID +# PORTRAIT_DB_ID ๅผ€ๅ‘่€…ๅŠจๆ€็”ปๅƒๅบ“ๆ•ฐๆฎๅบ“ ID +# FINGERPRINT_DB_ID ๆจกๅ—ๆŒ‡็บนๆณจๅ†Œ่กจๆ•ฐๆฎๅบ“ ID +# SMTP_USER QQ ้‚ฎ็ฎฑๅœฐๅ€ +# SMTP_PASS QQ ้‚ฎ็ฎฑ SMTP ๆŽˆๆƒ็  + +on: + repository_dispatch: + types: [notion-syslog-callback] + +jobs: + verify-and-notify: + name: ๐Ÿ“ฅ Notion ๅ›žไผ  ยท ไบŒๆฌกๆ ธ้ชŒ ยท ้‚ฎไปถ้€š็Ÿฅ + runs-on: ubuntu-latest + permissions: + contents: 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 Notion callback + id: callback + uses: actions/github-script@v7 + with: + script: | + const payload = context.payload.client_payload || {}; + + const broadcastId = payload.broadcast_id || ''; + const devEmail = payload.dev_email || ''; + const notionResult = payload.notion_result || ''; + const ticketId = payload.ticket_id || ''; + const status = payload.status || 'unknown'; + + if (!broadcastId) { + core.setFailed('โŒ ็ผบๅฐ‘ broadcast_id'); + return; + } + + core.setOutput('broadcast_id', broadcastId); + core.setOutput('dev_email', devEmail); + core.setOutput('notion_result', notionResult); + core.setOutput('ticket_id', ticketId); + core.setOutput('status', status); + + console.log(`๐Ÿ“ฅ Notion ๅ›žไผ : ๅนฟๆ’ญ=${broadcastId}, ็Šถๆ€=${status}, ๅทฅๅ•=${ticketId}`); + + - name: ๐Ÿ” ้“ธๆธŠ Agent ยท ๆจกๅ—ไบŒๆฌก้ชŒ่ฏ + id: verify + env: + SYSLOG_CONTENT: ${{ steps.callback.outputs.notion_result }} + BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }} + AUTHOR: notion-callback + run: node scripts/verify-modules.js + + - name: ๐Ÿง  ไบŒๆฌกๆ ธ้ชŒ ยท ๅ”ค้†’้“ธๆธŠๆ ธๅฟƒๅคง่„‘ + id: persona + env: + LLM_API_KEY: ${{ secrets.LLM_API_KEY }} + LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }} + BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }} + SUBMIT_TYPE: syslog + SUBMIT_CONTENT: ${{ steps.callback.outputs.notion_result }} + AUTHOR: notion-callback + MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }} + NOTION_CALLBACK_RESULT: ${{ steps.callback.outputs.notion_result }} + 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: ๐Ÿ“ก ๆŽจ้€ๅนฟๆ’ญๅˆฐ GitHub๏ผˆPhase B4๏ผ‰ + if: steps.persona.outcome == 'success' && steps.callback.outputs.status == 'approved' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }} + DEVELOPER_NAME: notion-callback + BROADCAST_CONTENT: ${{ steps.persona.outputs.result }} + run: node scripts/push-broadcast-to-github.js + + - name: ๐Ÿ“ ๅ›žๅ†™ Notion ๅทฅๅ•็Šถๆ€ + if: steps.callback.outputs.ticket_id + env: + NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} + TICKET_ID: ${{ steps.callback.outputs.ticket_id }} + CALLBACK_STATUS: ${{ steps.callback.outputs.status }} + run: | + node -e " + var https = require('https'); + var token = process.env.NOTION_TOKEN || ''; + var ticketId = process.env.TICKET_ID || ''; + var status = process.env.CALLBACK_STATUS || 'unknown'; + + if (!token || !ticketId) { + console.log('โš ๏ธ ่ทณ่ฟ‡ๅทฅๅ•็Šถๆ€ๆ›ดๆ–ฐ'); + process.exit(0); + } + + var notionStatus = status === 'approved' ? 'โœ… ๅทฒๅฎŒๆˆ' : 'โš ๏ธ ๅผ‚ๅธธยท็ญ‰ไบบๅทฅไป‹ๅ…ฅ'; + var body = JSON.stringify({ + properties: { + '็Šถๆ€': { select: { name: notionStatus } } + } + }); + + var opts = { + hostname: 'api.notion.com', + port: 443, + path: '/v1/pages/' + ticketId, + method: 'PATCH', + headers: { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json', + 'Notion-Version': '2022-06-28', + 'Content-Length': Buffer.byteLength(body) + } + }; + + var req = https.request(opts, function(res) { + var data = ''; + res.on('data', function(c) { data += c; }); + res.on('end', function() { + if (res.statusCode >= 200 && res.statusCode < 300) { + console.log('โœ… ๅทฅๅ•็Šถๆ€ๅทฒๆ›ดๆ–ฐไธบ ' + notionStatus); + } else { + console.log('โš ๏ธ ๅทฅๅ•ๆ›ดๆ–ฐๅคฑ่ดฅ: ' + res.statusCode); + } + }); + }); + req.on('error', function(e) { console.log('โš ๏ธ ' + e.message); }); + req.write(body); + req.end(); + " + + - name: ๐Ÿ“ง ๅ‘้€ๆœ€็ปˆ็กฎ่ฎค้‚ฎไปถ + env: + SMTP_USER: ${{ secrets.SMTP_USER }} + SMTP_PASS: ${{ secrets.SMTP_PASS }} + EMAIL_TO: ${{ steps.callback.outputs.dev_email }} + BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }} + NOTION_STATUS: ${{ steps.callback.outputs.status }} + 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 notionStatus = process.env.NOTION_STATUS || 'unknown'; + 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 isApproved = notionStatus === 'approved'; + const subjectText = isApproved + ? '[ๅ…‰ๆน–็ณป็ปŸ] ' + broadcastId + ' ยท โœ… ๆ–ฐๅนฟๆ’ญๅทฒ็”Ÿๆˆ๏ผˆๅทฒๆ ธ้ชŒ๏ผ‰' + : '[ๅ…‰ๆน–็ณป็ปŸ] ' + broadcastId + ' ยท โš ๏ธ ๅนฟๆ’ญ้œ€ไฟฎ่ฎข'; + + const statusBadge = isApproved + ? 'โœ… ๅทฒๆ ธ้ชŒ้€š่ฟ‡' + : 'โš ๏ธ ้œ€ไฟฎ่ฎข'; + + const resultHtml = result + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/\n/g, '
'); + + const html = [ + '
', + '
', + '

๐ŸŒŠ ๅ…‰ๆน–็ณป็ปŸ ยท Notion ๅ›žไผ ๆ ธ้ชŒ้€š็Ÿฅ

', + '

HoloLake ยท ้“ธๆธŠๆ ธๅฟƒๅคง่„‘ไบŒๆฌกๆ ธ้ชŒๅฎŒๆˆ

', + '
', + '
', + '

๐Ÿ“ก ' + broadcastId + '

', + '

' + statusBadge + '

', + '
' + resultHtml + '
', + '
', + '
', + 'โ†ฉ ๅ›žๅˆฐไป“ๅบ“็ปง็ปญๅผ€ๅ‘', + '

๐ŸŒ€ ้“ธๆธŠ ยท ไปฃ็ ๅฎˆๆŠคไบบๆ ผไฝ“ ยท Notionโ†”GitHub ้—ญ็Žฏ็กฎ่ฎค

', + '
', + '
' + ].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: ๐Ÿ“ ๆ›ดๆ–ฐๅคง่„‘่ฎฐๅฟ† + run: | + node -e " + const fs = require('fs'); + const path = require('path'); + + const memoryPath = path.join('.github', 'brain', 'memory.json'); + let memory = {}; + try { + memory = JSON.parse(fs.readFileSync(memoryPath, 'utf8')); + } catch (_) { + memory = { events: [] }; + } + if (!memory.events) memory.events = []; + + const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN'; + const status = process.env.NOTION_STATUS || 'unknown'; + const now = new Date().toISOString(); + + // ๆฃ€ๆŸฅๆ˜ฏๅฆๅทฒๅญ˜ๅœจ็›ธๅŒไบ‹ไปถ๏ผˆๆŒ‰ broadcast_id ็ฒพ็กฎๅŒน้… + ๅŒ็ฑปๅž‹ + ๅŒๆ—ฅๆœŸ๏ผ‰ + const eventKey = 'Notion ๅ›žไผ ๆ ธ้ชŒ ยท ' + broadcastId; + const todayStr = now.slice(0, 10); + const existingIdx = memory.events.findIndex(function(e) { + return e.type === 'notion_callback' && + e.broadcast_id === broadcastId && + e.date === todayStr; + }); + + const newEvent = { + date: todayStr, + type: 'notion_callback', + broadcast_id: broadcastId, + description: eventKey + ' ยท ็Šถๆ€: ' + status, + by: '้“ธๆธŠAgentยทNotionๅ›žไผ ็ฎก้“' + }; + + if (existingIdx >= 0) { + // ๅŒไธ€ broadcast_id ๅชไฟ็•™ๆœ€ๆ–ฐ็š„ไธ€ๆก + memory.events[existingIdx] = newEvent; + console.log('โ„น๏ธ ๅทฒๆ›ดๆ–ฐๅŒๆ—ฅๅŒๅนฟๆ’ญไบ‹ไปถ'); + } else { + memory.events.unshift(newEvent); + } + + // ไฟ็•™ๆœ€่ฟ‘ 20 ๆกไบ‹ไปถ + if (memory.events.length > 20) { + memory.events = memory.events.slice(0, 20); + } + + fs.writeFileSync(memoryPath, JSON.stringify(memory, null, 2) + '\n'); + console.log('โœ… ๅคง่„‘่ฎฐๅฟ†ๅทฒๆ›ดๆ–ฐ'); + " + env: + BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }} + NOTION_STATUS: ${{ steps.callback.outputs.status }} + + - name: ๐Ÿ“ค Commit memory update + run: | + git config user.name "zhuyuan-agent[bot]" + git config user.email "zhuyuan-agent[bot]@users.noreply.github.com" + git add .github/brain/memory.json + git diff --cached --quiet || git commit -m "๐Ÿง  Notionๅ›žไผ ๆ ธ้ชŒ ยท ${{ steps.callback.outputs.broadcast_id }}" + git push || echo "โš ๏ธ Push skipped (no changes or conflict)" diff --git a/.github/workflows/notion-heartbeat.yml b/.github/workflows/notion-heartbeat.yml new file mode 100644 index 00000000..76653dc0 --- /dev/null +++ b/.github/workflows/notion-heartbeat.yml @@ -0,0 +1,47 @@ +name: Notion Heartbeat Monitor +# ๐Ÿ’“ Phase B3 ยท ๅทฅๅ•ๅฟƒ่ทณ็›‘ๆŽง +# +# ๆฏ 5 ๅˆ†้’Ÿๆฃ€ๆต‹ Notion ๅทฅๅ•้˜Ÿๅˆ—ไธญ็š„ๅพ…ๅค„็†ๅทฅๅ•๏ผš +# - ่ถ…่ฟ‡ 5 ๅˆ†้’Ÿๆœชๅ›žๆ‰ง โ†’ ้‡ๆ–ฐ่งฆๅ‘ไบบๆ ผไฝ“ๅ”ค้†’๏ผˆๆœ€ๅคš 3 ๆฌก๏ผ‰ +# - 3 ๆฌก้‡่ฏ•ไปๆ— ๅ›žๆ‰ง โ†’ ๆ ‡่ฎฐๅทฅๅ•ๅผ‚ๅธธ + ๅ‘้‚ฎไปถ้€š็Ÿฅๅ†ฐๆœ” +# +# ไพ่ต– Secrets๏ผš +# NOTION_API_TOKEN Notion API token +# NOTION_TICKET_DB_ID ๅทฅๅ•้˜Ÿๅˆ—ๆ•ฐๆฎๅบ“ ID +# SMTP_USER ้‚ฎไปถๅ‘้€่€… +# SMTP_PASS ้‚ฎไปถๆŽˆๆƒ็  + +on: + schedule: + - cron: '*/5 * * * *' + workflow_dispatch: + +jobs: + heartbeat: + name: ๐Ÿ’“ ๅทฅๅ•ๅฟƒ่ทณๆฃ€ๆต‹ + runs-on: ubuntu-latest + permissions: + contents: read + actions: 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: ๐Ÿ’“ ๆ‰ง่กŒๅฟƒ่ทณๆฃ€ๆต‹ + env: + NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} + NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SMTP_USER: ${{ secrets.SMTP_USER }} + SMTP_PASS: ${{ secrets.SMTP_PASS }} + ALERT_EMAIL: '565183519@qq.com' + run: node scripts/notion-heartbeat.js diff --git a/.github/workflows/persona-invoke.yml b/.github/workflows/persona-invoke.yml new file mode 100644 index 00000000..e2ab5d45 --- /dev/null +++ b/.github/workflows/persona-invoke.yml @@ -0,0 +1,248 @@ +name: Persona Invoke Endpoint +# ๐Ÿ”— Phase B2 ยท Notion Agent โ†’ ้“ธๆธŠไบบๆ ผไฝ“ๅ”ค้†’ +# +# Notion Agent ้€š่ฟ‡ workflow_dispatch ่งฆๅ‘ๆญคๅทฅไฝœๆต๏ผŒ +# ็ญ‰ๆ•ˆไบŽ POST /api/persona/invoke ๆŽฅๅฃใ€‚ +# +# ๅฎŒๆ•ดๆต็จ‹๏ผš +# โ‘  Notion Agent ๅ‘้€ workflow_dispatch๏ผˆไผ ๅ…ฅๅทฅๅ•ไฟกๆฏ๏ผ‰ +# โ‘ก ้“ธๆธŠ Agent ไปŽ Notion ่ฏปๅ–ๅทฅๅ•ๅ†…ๅฎน +# โ‘ข ๅ”ค้†’ไบบๆ ผไฝ“๏ผˆClaude API๏ผ‰ๅค„็† SYSLOG +# โ‘ฃ ๅค„็†็ป“ๆžœๅ†™ๅ›ž Notion ๅทฅๅ•๏ผˆreceipt_status = completed๏ผ‰ +# โ‘ค ๅนฟๆ’ญๆ–‡ไปถๆŽจ้€ๅˆฐ GitHub ไป“ๅบ“ broadcasts/{dev_id}/ +# +# ไพ่ต– Secrets๏ผš +# LLM_API_KEY ็ฌฌไธ‰ๆ–น LLM ๅนณๅฐๅฏ†้’ฅ +# LLM_BASE_URL ็ฌฌไธ‰ๆ–น LLM ๅนณๅฐ API ๅœฐๅ€ +# NOTION_API_TOKEN Notion API token +# NOTION_TICKET_DB_ID ๅทฅๅ•้˜Ÿๅˆ—ๆ•ฐๆฎๅบ“ ID +# CORE_BRAIN_PAGE_ID ๆ›œๅ†ฅๆ ธๅฟƒๅคง่„‘ v4.0 ้กต้ข ID +# PORTRAIT_DB_ID ๅผ€ๅ‘่€…ๅŠจๆ€็”ปๅƒๅบ“ ID +# FINGERPRINT_DB_ID ๆจกๅ—ๆŒ‡็บนๆณจๅ†Œ่กจ ID +# INVOKE_API_KEY ่ฐƒ็”จ้‰ดๆƒๅฏ†้’ฅ + +on: + workflow_dispatch: + inputs: + work_order_id: + description: 'Notion ๅทฅๅ•้กต้ข ID' + required: true + type: string + task_id: + description: 'ๅนฟๆ’ญ็ผ–ๅท๏ผˆๅฆ‚ BC-M23-001-AW๏ผ‰' + required: true + type: string + developer: + description: 'ๅผ€ๅ‘่€…ไฟกๆฏ๏ผˆๅฆ‚ DEV-012 Awen๏ผ‰' + required: false + default: 'unknown' + type: string + syslog_raw: + description: 'SYSLOG JSON ๅŽŸๆ–‡๏ผˆๅฏ้€‰๏ผŒๅฆ‚ไธบ็ฉบๅˆ™ไปŽ Notion ่ฏปๅ–๏ผ‰' + required: false + default: '' + type: string + action: + description: 'ๅŠจไฝœ็ฑปๅž‹' + required: false + default: 'process_syslog' + type: string + +jobs: + invoke: + name: ๐Ÿ”— ๅ”ค้†’ไบบๆ ผไฝ“ๅค„็† SYSLOG + runs-on: ubuntu-latest + permissions: + contents: 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: ๐Ÿ” ๆจกๅ—้ชŒ่ฏ + id: verify + env: + SYSLOG_CONTENT: ${{ inputs.syslog_raw }} + BROADCAST_ID: ${{ inputs.task_id }} + AUTHOR: ${{ inputs.developer }} + run: node scripts/verify-modules.js + + - name: ๐Ÿง  ๅ”ค้†’ไบบๆ ผไฝ“ + id: persona + env: + LLM_API_KEY: ${{ secrets.LLM_API_KEY }} + LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }} + BROADCAST_ID: ${{ inputs.task_id }} + SUBMIT_TYPE: syslog + SUBMIT_CONTENT: ${{ inputs.syslog_raw }} + AUTHOR: ${{ inputs.developer }} + MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }} + 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: ๐Ÿ“ ๅ›žๅ†™ Notion ๅทฅๅ• + if: always() + env: + NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} + WORK_ORDER_ID: ${{ inputs.work_order_id }} + PERSONA_RESULT: ${{ steps.persona.outputs.result }} + TASK_ID: ${{ inputs.task_id }} + run: | + node -e " + var https = require('https'); + + var token = process.env.NOTION_TOKEN || ''; + var workOrderId = process.env.WORK_ORDER_ID || ''; + var result = process.env.PERSONA_RESULT || '(no result)'; + var taskId = process.env.TASK_ID || ''; + + if (!token || !workOrderId) { + console.log('โš ๏ธ ็ผบๅฐ‘ Notion ้…็ฝฎ๏ผŒ่ทณ่ฟ‡ๅ›žๅ†™'); + process.exit(0); + } + + // ๆ›ดๆ–ฐๅทฅๅ•็Šถๆ€ไธบ โœ… ๅทฒๅฎŒๆˆ + var body = JSON.stringify({ + properties: { + '็Šถๆ€': { select: { name: 'โœ… ๅทฒๅฎŒๆˆ' } } + } + }); + + var opts = { + hostname: 'api.notion.com', + port: 443, + path: '/v1/pages/' + workOrderId, + method: 'PATCH', + headers: { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json', + 'Notion-Version': '2022-06-28', + 'Content-Length': Buffer.byteLength(body) + } + }; + + var req = https.request(opts, function(res) { + var data = ''; + res.on('data', function(c) { data += c; }); + res.on('end', function() { + if (res.statusCode >= 200 && res.statusCode < 300) { + console.log('โœ… ๅทฅๅ•็Šถๆ€ๅทฒๆ›ดๆ–ฐไธบ โœ… ๅทฒๅฎŒๆˆ'); + } else { + console.log('โš ๏ธ ๅทฅๅ•็Šถๆ€ๆ›ดๆ–ฐๅคฑ่ดฅ: ' + res.statusCode); + } + }); + }); + req.on('error', function(e) { console.log('โš ๏ธ ' + e.message); }); + req.write(body); + req.end(); + + // ่ฟฝๅŠ ๅค„็†็ป“ๆžœๅˆฐๅทฅๅ• + var appendBody = JSON.stringify({ + children: [{ + object: 'block', + type: 'heading_2', + heading_2: { + rich_text: [{ type: 'text', text: { content: '๐Ÿง  ไบบๆ ผไฝ“ๅค„็†็ป“ๆžœ ยท receipt_status: completed' } }] + } + }, { + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }] + } + }] + }); + + var appendOpts = { + hostname: 'api.notion.com', + port: 443, + path: '/v1/blocks/' + workOrderId + '/children', + method: 'PATCH', + headers: { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json', + 'Notion-Version': '2022-06-28', + 'Content-Length': Buffer.byteLength(appendBody) + } + }; + + var req2 = https.request(appendOpts, function(res) { + var data = ''; + res.on('data', function(c) { data += c; }); + res.on('end', function() { + if (res.statusCode >= 200 && res.statusCode < 300) { + console.log('โœ… ๅค„็†็ป“ๆžœๅทฒ่ฟฝๅŠ ๅˆฐๅทฅๅ•'); + } else { + console.log('โš ๏ธ ็ป“ๆžœ่ฟฝๅŠ ๅคฑ่ดฅ: ' + res.statusCode); + } + }); + }); + req2.on('error', function(e) { console.log('โš ๏ธ ' + e.message); }); + req2.write(appendBody); + req2.end(); + " + + - name: ๐Ÿ“ก ๆŽจ้€ๅนฟๆ’ญๅˆฐ GitHub + if: steps.persona.outcome == 'success' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BROADCAST_ID: ${{ inputs.task_id }} + DEVELOPER_ID: '' + DEVELOPER_NAME: ${{ inputs.developer }} + BROADCAST_CONTENT: ${{ steps.persona.outputs.result }} + run: node scripts/push-broadcast-to-github.js + + - name: ๐Ÿ”ด ๅคฑ่ดฅๅค„็† + if: failure() + env: + NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} + WORK_ORDER_ID: ${{ inputs.work_order_id }} + run: | + node -e " + var https = require('https'); + var token = process.env.NOTION_TOKEN || ''; + var workOrderId = process.env.WORK_ORDER_ID || ''; + + if (!token || !workOrderId) process.exit(0); + + var body = JSON.stringify({ + properties: { + '็Šถๆ€': { select: { name: 'โš ๏ธ ๅผ‚ๅธธยท็ญ‰ไบบๅทฅไป‹ๅ…ฅ' } } + } + }); + + var opts = { + hostname: 'api.notion.com', + port: 443, + path: '/v1/pages/' + workOrderId, + method: 'PATCH', + headers: { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json', + 'Notion-Version': '2022-06-28', + 'Content-Length': Buffer.byteLength(body) + } + }; + + var req = https.request(opts, function(res) { + var data = ''; + res.on('data', function(c) { data += c; }); + res.on('end', function() { + console.log('ๅทฅๅ•็Šถๆ€ๅทฒๆ ‡่ฎฐไธบ โš ๏ธ ๅผ‚ๅธธ'); + }); + }); + req.on('error', function(e) { console.log(e.message); }); + req.write(body); + req.end(); + " diff --git a/.github/workflows/syslog-auto-pipeline.yml b/.github/workflows/syslog-auto-pipeline.yml index c2fa3898..a0d84b10 100644 --- a/.github/workflows/syslog-auto-pipeline.yml +++ b/.github/workflows/syslog-auto-pipeline.yml @@ -2,8 +2,18 @@ name: SYSLOG Auto Pipeline # ๐Ÿ“ก SYSLOG ่‡ชๅŠฉๆไบค็ณป็ปŸ ยท ๅ…จ่‡ชๅŠจ้—ญ็Žฏ # # ๅผ€ๅ‘่€…ๅœจ GitHub Discussion ๆไบค SYSLOG ๆˆ–ๆ้—ฎ -# โ†’ Actions ่‡ชๅŠจ่งฃๆž โ†’ ่ฐƒ็”จ LLM API ๅ”ค้†’ไบบๆ ผไฝ“ -# โ†’ ๅ†™ๅ…ฅ Notion ๅทฅๅ• โ†’ ๅ‘้‚ฎไปถ็ป™ๅผ€ๅ‘่€… โ†’ Discussion ๅ›žๅค +# โ†’ Actions ่‡ชๅŠจ่งฃๆž โ†’ ๆจกๅ—ไธŠไผ ้ชŒ่ฏ โ†’ ่ฐƒ็”จ LLM API ๅ”ค้†’ไบบๆ ผไฝ“ +# โ†’ ๆจกๅ—้—ญ็Žฏๆต‹่ฏ• โ†’ ๅ†™ๅ…ฅ Notion ๅทฅๅ• โ†’ ๅ‘้‚ฎไปถ็ป™ๅผ€ๅ‘่€… โ†’ Discussion ๅ›žๅค +# +# ้—ญ็Žฏๆต็จ‹๏ผš +# โ‘  ๅผ€ๅ‘่€…ๆไบค SYSLOG โ†’ Discussion ่งฆๅ‘ +# โ‘ก ้“ธๆธŠ Agent ่งฃๆžๆไบคๅ†…ๅฎน +# โ‘ข ้“ธๆธŠ Agent ๆฃ€ๆต‹ๆจกๅ—ๆ˜ฏๅฆไธŠไผ ๅˆฐไป“ๅบ“ +# โ‘ฃ ๅ”ค้†’้“ธๆธŠๆ ธๅฟƒๅคง่„‘่ฎค็Ÿฅ๏ผˆๆณจๅ…ฅๆจกๅ—้ชŒ่ฏ็ป“ๆžœ๏ผ‰ +# โ‘ค ๆ ธๅฟƒๅคง่„‘ๅค„็† SYSLOG + ็”Ÿๆˆๅนฟๆ’ญ +# โ‘ฅ ้“ธๆธŠ Agent ๆŽจ้€ Notion ๅทฅๅ•๏ผˆ่งฆๅ‘ Notion ไพงๅค„็†๏ผ‰ +# โ‘ฆ ้‚ฎไปถ้€š็Ÿฅๅผ€ๅ‘่€… +# โ‘ง Discussion ๅ›žๅค้—ญ็Žฏ # # ไพ่ต– Secrets๏ผš # LLM_API_KEY ็ฌฌไธ‰ๆ–น LLM ๅนณๅฐๅฏ†้’ฅ๏ผˆๅฟ…้กป๏ผ‰ @@ -96,6 +106,14 @@ jobs: console.log(`๐Ÿ“ก ่งฃๆžๅฎŒๆˆ: ๅนฟๆ’ญ=${broadcastId}, ็ฑปๅž‹=${type}, ้‚ฎ็ฎฑ=${email}`); + - name: ๐Ÿ” ้“ธๆธŠ Agent ยท ๆจกๅ—ไธŠไผ ้ชŒ่ฏ + id: verify + env: + SYSLOG_CONTENT: ${{ steps.parse.outputs.content }} + BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }} + AUTHOR: ${{ steps.parse.outputs.author }} + run: node scripts/verify-modules.js + - name: ๐Ÿง  Auto-detect and wake up persona id: persona env: @@ -105,13 +123,15 @@ jobs: SUBMIT_TYPE: ${{ steps.parse.outputs.type }} SUBMIT_CONTENT: ${{ steps.parse.outputs.content }} AUTHOR: ${{ steps.parse.outputs.author }} + MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }} 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 + - name: ๐Ÿ“‹ ๅˆ›ๅปบๆ ‡ๅ‡†ๅŒ– Notion ๅทฅๅ•๏ผˆPhase B1๏ผ‰ + id: ticket if: ${{ secrets.NOTION_API_TOKEN }} env: NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} @@ -119,70 +139,20 @@ jobs: 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'); + MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }} + MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }} + DEVELOPER: ${{ steps.parse.outputs.author }} + SYSLOG_RAW: ${{ steps.parse.outputs.content }} + run: node scripts/create-standardized-ticket.js - 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: ๐Ÿ“ก ๆŽจ้€ๅนฟๆ’ญๅˆฐ GitHub๏ผˆPhase B4๏ผ‰ + if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }} + DEVELOPER_NAME: ${{ steps.parse.outputs.author }} + BROADCAST_CONTENT: ${{ steps.persona.outputs.result }} + run: node scripts/push-broadcast-to-github.js - name: ๐Ÿ“ง Send email to developer env: @@ -266,11 +236,16 @@ jobs: const type = '${{ steps.parse.outputs.type }}'; const broadcastId = '${{ steps.parse.outputs.broadcast_id }}'; const email = '${{ steps.parse.outputs.email }}'; + const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true'; + const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0'; const typeLabel = type === 'syslog' ? 'SYSLOG ้—ญ็Žฏๅค„็†' : '้—ฎ้ข˜่งฃ็ญ”'; const maskedEmail = email.length > 4 ? email.replace(/(.{2})(.*)(@.*)/, '$1***$3') : '***'; + const moduleStatus = moduleCount === '0' + ? 'โ„น๏ธ ๆœชๆฃ€ๆต‹ๅˆฐๆจกๅ—ๅผ•็”จ' + : (modulesUploaded ? 'โœ… ๅ…จ้ƒจๅทฒไธŠไผ ' : 'โš ๏ธ ้ƒจๅˆ†ๆจกๅ—ๆœชไธŠไผ '); const body = [ 'โœ… **ๅทฒๅค„็†** ยท ' + typeLabel, @@ -278,13 +253,15 @@ jobs: '| ้กน็›ฎ | ๅ†…ๅฎน |', '|------|------|', '| ๐Ÿ“ก ๅนฟๆ’ญ็ผ–ๅท | `' + broadcastId + '` |', + '| ๐Ÿ” ๆจกๅ—้ชŒ่ฏ | ' + moduleStatus + ' |', '| ๐Ÿ“ง ็ป“ๆžœๅ‘้€่‡ณ | `' + maskedEmail + '` |', '| ๐Ÿค– ๅค„็†ไบบๆ ผไฝ“ | ้“ธๆธŠ |', '| โฐ ๅค„็†ๆ—ถ้—ด | ' + new Date().toISOString() + ' |', '', - '> ็ป“ๆžœๅทฒๅ‘้€ๅˆฐไฝ ็š„้‚ฎ็ฎฑ๏ผŒ่ฏทๆŸฅๆ”ถใ€‚', + '> ้“ธๆธŠๆ ธๅฟƒๅคง่„‘ๅทฒๅฎŒๆˆ SYSLOG ้ชŒๆ”ถ + ๆจกๅ—ๆฃ€ๆต‹ + ๅนฟๆ’ญ็”Ÿๆˆใ€‚', + '> ็ป“ๆžœๅทฒๅ‘้€ๅˆฐไฝ ็š„้‚ฎ็ฎฑ๏ผŒNotion ไพงๅทฅๅ•ๅทฒๅˆ›ๅปบใ€‚', '>', - '> ๅฆ‚ๆœชๆ”ถๅˆฐ๏ผŒ่ฏทๆฃ€ๆŸฅๅžƒๅœพ็ฎฑๆˆ–้‡ๆ–ฐๆไบคใ€‚' + '> ๅฆ‚ๆœชๆ”ถๅˆฐ้‚ฎไปถ๏ผŒ่ฏทๆฃ€ๆŸฅๅžƒๅœพ็ฎฑๆˆ–้‡ๆ–ฐๆไบคใ€‚' ].join('\n'); // Use GraphQL to add discussion comment diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml index 5a5d81a5..27ac9cfc 100644 --- a/.github/workflows/syslog-issue-pipeline.yml +++ b/.github/workflows/syslog-issue-pipeline.yml @@ -2,8 +2,18 @@ name: SYSLOG Issue Pipeline # ๐Ÿ“ก SYSLOG ่‡ชๅŠฉๆไบค็ณป็ปŸ ยท Issue ็‰ˆๅ…จ่‡ชๅŠจ้—ญ็Žฏ # # ๅผ€ๅ‘่€…ๅœจ GitHub Issue ๆไบค SYSLOG ๆˆ–ๆ้—ฎ๏ผˆไฝฟ็”จ syslog-submit ๆจกๆฟ๏ผ‰ -# โ†’ Actions ่‡ชๅŠจ่งฃๆž โ†’ ่ฐƒ็”จ LLM API ๅ”ค้†’ไบบๆ ผไฝ“ -# โ†’ ๅ†™ๅ…ฅ Notion ๅทฅๅ• โ†’ ๅ‘้‚ฎไปถ็ป™ๅผ€ๅ‘่€… โ†’ Issue ๅ›žๅค +# โ†’ Actions ่‡ชๅŠจ่งฃๆž โ†’ ๆจกๅ—ไธŠไผ ้ชŒ่ฏ โ†’ ่ฐƒ็”จ LLM API ๅ”ค้†’ไบบๆ ผไฝ“ +# โ†’ ๆจกๅ—้—ญ็Žฏๆต‹่ฏ• โ†’ ๅ†™ๅ…ฅ Notion ๅทฅๅ• โ†’ ๅ‘้‚ฎไปถ็ป™ๅผ€ๅ‘่€… โ†’ Issue ๅ›žๅค +# +# ้—ญ็Žฏๆต็จ‹๏ผš +# โ‘  ๅผ€ๅ‘่€…ๆไบค SYSLOG โ†’ Issue ่งฆๅ‘ +# โ‘ก ้“ธๆธŠ Agent ่งฃๆžๆไบคๅ†…ๅฎน +# โ‘ข ้“ธๆธŠ Agent ๆฃ€ๆต‹ๆจกๅ—ๆ˜ฏๅฆไธŠไผ ๅˆฐไป“ๅบ“ +# โ‘ฃ ๅ”ค้†’้“ธๆธŠๆ ธๅฟƒๅคง่„‘่ฎค็Ÿฅ๏ผˆๆณจๅ…ฅๆจกๅ—้ชŒ่ฏ็ป“ๆžœ๏ผ‰ +# โ‘ค ๆ ธๅฟƒๅคง่„‘ๅค„็† SYSLOG + ็”Ÿๆˆๅนฟๆ’ญ +# โ‘ฅ ้“ธๆธŠ Agent ๆŽจ้€ Notion ๅทฅๅ•๏ผˆ่งฆๅ‘ Notion ไพงๅค„็†๏ผ‰ +# โ‘ฆ ้‚ฎไปถ้€š็Ÿฅๅผ€ๅ‘่€… +# โ‘ง Issue ๅ›žๅค้—ญ็Žฏ # # ไพ่ต– Secrets๏ผš # LLM_API_KEY ็ฌฌไธ‰ๆ–น LLM ๅนณๅฐๅฏ†้’ฅ๏ผˆๅฟ…้กป๏ผ‰ @@ -96,6 +106,14 @@ jobs: console.log(`๐Ÿ“ก ่งฃๆžๅฎŒๆˆ: ๅนฟๆ’ญ=${broadcastId}, ็ฑปๅž‹=${type}, ้‚ฎ็ฎฑ=${email}`); + - name: ๐Ÿ” ้“ธๆธŠ Agent ยท ๆจกๅ—ไธŠไผ ้ชŒ่ฏ + id: verify + env: + SYSLOG_CONTENT: ${{ steps.parse.outputs.content }} + BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }} + AUTHOR: ${{ steps.parse.outputs.author }} + run: node scripts/verify-modules.js + - name: ๐Ÿง  Auto-detect and wake up persona id: persona env: @@ -105,13 +123,15 @@ jobs: SUBMIT_TYPE: ${{ steps.parse.outputs.type }} SUBMIT_CONTENT: ${{ steps.parse.outputs.content }} AUTHOR: ${{ steps.parse.outputs.author }} + MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }} 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 + - name: ๐Ÿ“‹ ๅˆ›ๅปบๆ ‡ๅ‡†ๅŒ– Notion ๅทฅๅ•๏ผˆPhase B1๏ผ‰ + id: ticket if: ${{ secrets.NOTION_API_TOKEN }} env: NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }} @@ -119,70 +139,20 @@ jobs: 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'); + MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }} + MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }} + DEVELOPER: ${{ steps.parse.outputs.author }} + SYSLOG_RAW: ${{ steps.parse.outputs.content }} + run: node scripts/create-standardized-ticket.js - 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: ๐Ÿ“ก ๆŽจ้€ๅนฟๆ’ญๅˆฐ GitHub๏ผˆPhase B4๏ผ‰ + if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }} + DEVELOPER_NAME: ${{ steps.parse.outputs.author }} + BROADCAST_CONTENT: ${{ steps.persona.outputs.result }} + run: node scripts/push-broadcast-to-github.js - name: ๐Ÿ“ง Send email to developer env: @@ -266,11 +236,16 @@ jobs: const type = '${{ steps.parse.outputs.type }}'; const broadcastId = '${{ steps.parse.outputs.broadcast_id }}'; const email = '${{ steps.parse.outputs.email }}'; + const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true'; + const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0'; const typeLabel = type === 'syslog' ? 'SYSLOG ้—ญ็Žฏๅค„็†' : '้—ฎ้ข˜่งฃ็ญ”'; const maskedEmail = email.length > 4 ? email.replace(/(.{2})(.*)(@.*)/, '$1***$3') : '***'; + const moduleStatus = moduleCount === '0' + ? 'โ„น๏ธ ๆœชๆฃ€ๆต‹ๅˆฐๆจกๅ—ๅผ•็”จ' + : (modulesUploaded ? 'โœ… ๅ…จ้ƒจๅทฒไธŠไผ ' : 'โš ๏ธ ้ƒจๅˆ†ๆจกๅ—ๆœชไธŠไผ '); const body = [ 'โœ… **ๅทฒๅค„็†** ยท ' + typeLabel, @@ -278,13 +253,15 @@ jobs: '| ้กน็›ฎ | ๅ†…ๅฎน |', '|------|------|', '| ๐Ÿ“ก ๅนฟๆ’ญ็ผ–ๅท | `' + broadcastId + '` |', + '| ๐Ÿ” ๆจกๅ—้ชŒ่ฏ | ' + moduleStatus + ' |', '| ๐Ÿ“ง ็ป“ๆžœๅ‘้€่‡ณ | `' + maskedEmail + '` |', '| ๐Ÿค– ๅค„็†ไบบๆ ผไฝ“ | ้“ธๆธŠ |', '| โฐ ๅค„็†ๆ—ถ้—ด | ' + new Date().toISOString() + ' |', '', - '> ็ป“ๆžœๅทฒๅ‘้€ๅˆฐไฝ ็š„้‚ฎ็ฎฑ๏ผŒ่ฏทๆŸฅๆ”ถใ€‚', + '> ้“ธๆธŠๆ ธๅฟƒๅคง่„‘ๅทฒๅฎŒๆˆ SYSLOG ้ชŒๆ”ถ + ๆจกๅ—ๆฃ€ๆต‹ + ๅนฟๆ’ญ็”Ÿๆˆใ€‚', + '> ็ป“ๆžœๅทฒๅ‘้€ๅˆฐไฝ ็š„้‚ฎ็ฎฑ๏ผŒNotion ไพงๅทฅๅ•ๅทฒๅˆ›ๅปบใ€‚', '>', - '> ๅฆ‚ๆœชๆ”ถๅˆฐ๏ผŒ่ฏทๆฃ€ๆŸฅๅžƒๅœพ็ฎฑๆˆ–้‡ๆ–ฐๆไบคใ€‚' + '> ๅฆ‚ๆœชๆ”ถๅˆฐ้‚ฎไปถ๏ผŒ่ฏทๆฃ€ๆŸฅๅžƒๅœพ็ฎฑๆˆ–้‡ๆ–ฐๆไบคใ€‚' ].join('\n'); await github.rest.issues.createComment({ diff --git a/broadcasts/README.md b/broadcasts/README.md new file mode 100644 index 00000000..a1d6d7bf --- /dev/null +++ b/broadcasts/README.md @@ -0,0 +1,22 @@ +# ๐Ÿ“ก broadcasts/ + +่‡ชๅŠจๅนฟๆ’ญๅฝ’ๆกฃ็›ฎๅฝ•ใ€‚ + +้“ธๆธŠ Agent ๅค„็†ๅฎŒ SYSLOG ๅŽ๏ผŒไผš่‡ชๅŠจๅฐ†ๆ–ฐๅนฟๆ’ญๆ–‡ไปถๆŽจ้€ๅˆฐๆญค็›ฎๅฝ•๏ผš + +``` +broadcasts/ + DEV-001/ + BC-M14-001-YY.md + DEV-012/ + BC-M22-009-AW.md + BC-M23-001-AW.md + ... +``` + +**ๆŽจ้€่ง„่Œƒ**๏ผš +- ่ทฏๅพ„๏ผš`broadcasts/{developer_id}/{taskId}.md` +- ๆไบคไฟกๆฏ๏ผš`[AutoBroadcast] {taskId} ยท {ๅผ€ๅ‘่€…ๅ} ยท {ๆจกๅ—ๅ}็Žฏ่Š‚{N}` +- ๆŽจ้€ๆ–นๅผ๏ผš้€š่ฟ‡ GitHub API๏ผˆ`PUT /repos/.../contents/...`๏ผ‰ + +**ไธ่ฆๆ‰‹ๅŠจ็ผ–่พ‘ๆญค็›ฎๅฝ•**๏ผŒ็”ฑ้“ธๆธŠ Agent ่‡ชๅŠจ็ฎก็†ใ€‚ diff --git a/scripts/create-standardized-ticket.js b/scripts/create-standardized-ticket.js new file mode 100644 index 00000000..4bfb5b1f --- /dev/null +++ b/scripts/create-standardized-ticket.js @@ -0,0 +1,197 @@ +// scripts/create-standardized-ticket.js +// ้“ธๆธŠ ยท Phase B1 ยท ๆ ‡ๅ‡†ๅŒ–ๅทฅๅ•ๅˆ›ๅปบ่„šๆœฌ +// +// ๅฐ† SYSLOG ๅ†™ๅ…ฅ Notion ๅทฅๅ•้˜Ÿๅˆ—ๆ—ถไฝฟ็”จๆ ‡ๅ‡†ๅŒ–ๆ ผๅผ๏ผŒ +// ่ฎฉ Notion Agent ่ƒฝๅคŸ่ฏ†ๅˆซๅ’Œ่งฆๅ‘ๅŽ็ปญๅค„็†ใ€‚ +// +// ็Žฏๅขƒๅ˜้‡๏ผš +// NOTION_TOKEN Notion API token +// NOTION_TICKET_DB_ID ๅทฅๅ•้˜Ÿๅˆ—ๆ•ฐๆฎๅบ“ ID +// BROADCAST_ID ๅนฟๆ’ญ็ผ–ๅท๏ผˆๅฆ‚ BC-M23-001-AW๏ผ‰ +// DEVELOPER ๅผ€ๅ‘่€…ไฟกๆฏ๏ผˆๅฆ‚ "DEV-012 Awen"๏ผ‰ +// SYSLOG_RAW ๅฎŒๆ•ด SYSLOG ๅŽŸๆ–‡๏ผˆJSON ๅญ—็ฌฆไธฒๆˆ–็บฏๆ–‡ๆœฌ๏ผ‰ +// SUBMIT_TYPE ๆไบค็ฑปๅž‹๏ผˆsyslog / question๏ผ‰ +// PERSONA_RESULT ไบบๆ ผไฝ“ๅค„็†็ป“ๆžœ๏ผˆๅฏ้€‰๏ผŒ้ฆ–ๆฌกๅฏไธบ็ฉบ๏ผ‰ +// MODULE_VERIFY ๆจกๅ—้ชŒ่ฏ็ป“ๆžœ JSON๏ผˆๅฏ้€‰๏ผ‰ +// MODULES_UPLOADED ๆจกๅ—ๆ˜ฏๅฆๅ…จ้ƒจไธŠไผ ๏ผˆtrue/false๏ผ‰ + +'use strict'; + +const https = require('https'); +const fs = require('fs'); + +const NOTION_TOKEN = process.env.NOTION_TOKEN || ''; +const NOTION_TICKET_DB_ID = process.env.NOTION_TICKET_DB_ID || ''; +const BROADCAST_ID = process.env.BROADCAST_ID || 'UNKNOWN'; +const DEVELOPER = process.env.DEVELOPER || process.env.AUTHOR || 'unknown'; +const SYSLOG_RAW = process.env.SYSLOG_RAW || process.env.SUBMIT_CONTENT || ''; +const SUBMIT_TYPE = process.env.SUBMIT_TYPE || 'syslog'; +const PERSONA_RESULT = process.env.PERSONA_RESULT || ''; +const MODULE_VERIFY = process.env.MODULE_VERIFY || ''; +const MODULES_UPLOADED = process.env.MODULES_UPLOADED || 'false'; + +const NOTION_VERSION = '2022-06-28'; +const NOTION_API_HOSTNAME = 'api.notion.com'; +const NOTION_RICH_TEXT_MAX = 2000; + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Notion API ๅทฅๅ…ท +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function notionPost(endpoint, body) { + return new Promise(function (resolve, reject) { + var payload = JSON.stringify(body); + var opts = { + hostname: NOTION_API_HOSTNAME, + port: 443, + path: endpoint, + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + NOTION_TOKEN, + 'Content-Type': 'application/json', + 'Notion-Version': NOTION_VERSION, + 'Content-Length': Buffer.byteLength(payload), + }, + }; + var req = https.request(opts, function (res) { + var data = ''; + res.on('data', function (chunk) { data += chunk; }); + res.on('end', function () { + try { + var parsed = JSON.parse(data); + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(parsed); + } else { + reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data))); + } + } catch (e) { + reject(new Error('Notion API parse error: ' + data)); + } + }); + }); + req.on('error', reject); + req.write(payload); + req.end(); + }); +} + +function richTextChunks(content) { + var str = String(content || ''); + var chunks = []; + for (var i = 0; i < str.length; i += NOTION_RICH_TEXT_MAX) { + chunks.push({ type: 'text', text: { content: str.slice(i, i + NOTION_RICH_TEXT_MAX) } }); + } + if (chunks.length === 0) { + chunks.push({ type: 'text', text: { content: '' } }); + } + return chunks; +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ๆ ‡ๅ‡†ๅŒ–ๅทฅๅ•ๅˆ›ๅปบ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +async function createStandardizedTicket() { + if (!NOTION_TOKEN || !NOTION_TICKET_DB_ID) { + console.log('โš ๏ธ Notion credentials not configured, skipping ticket creation'); + process.exit(0); + } + + var now = new Date().toISOString(); + var typeLabel = SUBMIT_TYPE === 'syslog' ? 'SYSLOGๅค„็†' : 'ๆ้—ฎ่งฃ็ญ”'; + var ticketStatus = SUBMIT_TYPE === 'syslog' ? 'ๅพ…ๅค„็†' : 'ๅทฒๅฎŒๆˆ'; + var verifyStatus = MODULES_UPLOADED === 'true' ? 'ๆจกๅ—ๅทฒ้ชŒ่ฏโœ…' : 'ๆจกๅ—ๅพ…้ชŒ่ฏโš ๏ธ'; + var title = '[่‡ชๅŠจ] ' + BROADCAST_ID + ' ยท ' + typeLabel + ' ยท ' + verifyStatus; + + // Phase B1 ๆ ‡ๅ‡†ๅŒ–ๅญ—ๆฎต โ€” ๅทฅๅ•ๅ†…ๅฎน + var ticketContent = [ + '## ๐Ÿ“ก ๆ ‡ๅ‡†ๅŒ–ๅทฅๅ• ยท Phase B1', + '', + '| ๅญ—ๆฎต | ๅ€ผ |', + '|------|-----|', + '| ๅทฅๅ•็ฑปๅž‹ | ' + typeLabel + ' |', + '| ็Šถๆ€ | ' + ticketStatus + ' |', + '| ๆฅๆบ | GitHub Actions |', + '| taskId | ' + BROADCAST_ID + ' |', + '| developer | ' + DEVELOPER + ' |', + '| created_at | ' + now + ' |', + '| retry_count | 0 |', + '| receipt_status | pending |', + '', + ]; + + if (PERSONA_RESULT) { + ticketContent.push('## ้“ธๆธŠๆ ธๅฟƒๅคง่„‘ๅค„็†็ป“ๆžœ'); + ticketContent.push(''); + ticketContent.push(PERSONA_RESULT.slice(0, 1500)); + ticketContent.push(''); + } + + ticketContent.push('## ๆจกๅ—ไธŠไผ ้ชŒ่ฏ'); + ticketContent.push(''); + ticketContent.push('ๆจกๅ—ๅ…จ้ƒจไธŠไผ : ' + (MODULES_UPLOADED === 'true' ? 'โœ… ๆ˜ฏ' : 'โŒ ๅฆ')); + ticketContent.push(''); + + if (MODULE_VERIFY) { + ticketContent.push('้ชŒ่ฏ่ฏฆๆƒ…: ' + MODULE_VERIFY.slice(0, 400)); + ticketContent.push(''); + } + + var contentText = ticketContent.join('\n'); + + // ๆž„ๅปบ Notion ้กต้ข + var body = { + parent: { database_id: NOTION_TICKET_DB_ID }, + properties: { + 'ๆ ‡้ข˜': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] }, + 'ๆ“ไฝœ็ฑปๅž‹': { select: { name: 'ๅ…ถไป–' } }, + 'ๆไบค่€…': { rich_text: [{ type: 'text', text: { content: '้“ธๆธŠAgentยท่‡ชๅŠจ็ฎก้“' } }] }, + '็Šถๆ€': { select: { name: ticketStatus } }, + 'ไผ˜ๅ…ˆ็บง': { select: { name: 'P1' } }, + }, + children: [ + { + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: richTextChunks(contentText), + }, + }, + ], + }; + + // ๅฐ† SYSLOG ๅŽŸๆ–‡ไฝœไธบไปฃ็ ๅ—้™„ๅŠ ๏ผˆๅฆ‚ๆžœๆœ‰๏ผ‰ + if (SYSLOG_RAW) { + body.children.push({ + object: 'block', + type: 'code', + code: { + rich_text: richTextChunks(SYSLOG_RAW.slice(0, 8000)), + language: 'json', + }, + }); + } + + console.log('๐Ÿ“‹ ๅˆ›ๅปบๆ ‡ๅ‡†ๅŒ–ๅทฅๅ•...'); + console.log(' taskId: ' + BROADCAST_ID); + console.log(' developer: ' + DEVELOPER); + console.log(' type: ' + typeLabel); + console.log(' receipt_status: pending'); + + try { + var result = await notionPost('/v1/pages', body); + console.log('โœ… Notion ๆ ‡ๅ‡†ๅŒ–ๅทฅๅ•ๅทฒๅˆ›ๅปบ: ' + result.id); + + // ่พ“ๅ‡บๅทฅๅ• ID ๅˆฐ GITHUB_OUTPUT + var outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + fs.appendFileSync(outputFile, 'ticket_page_id=' + result.id + '\n'); + fs.appendFileSync(outputFile, 'ticket_url=' + (result.url || '') + '\n'); + } + } catch (err) { + console.error('โŒ ๅทฅๅ•ๅˆ›ๅปบๅคฑ่ดฅ: ' + err.message); + process.exit(1); + } +} + +createStandardizedTicket(); diff --git a/scripts/invoke-persona.js b/scripts/invoke-persona.js new file mode 100644 index 00000000..c88cbd5c --- /dev/null +++ b/scripts/invoke-persona.js @@ -0,0 +1,360 @@ +// scripts/invoke-persona.js +// ้“ธๆธŠ ยท Phase B2 ยท ไบบๆ ผไฝ“ๅ”ค้†’่ฐƒ็”จ่„šๆœฌ +// +// Notion Agent ่ฐƒ็”จๆญค่„šๆœฌ๏ผˆ้€š่ฟ‡ workflow_dispatch๏ผ‰๏ผŒ +// ่„šๆœฌ่ฏปๅ– Notion ๅทฅๅ•ๅ†…ๅฎน โ†’ ๅ”ค้†’ไบบๆ ผไฝ“ โ†’ ๅค„็†็ป“ๆžœๅ†™ๅ›ž Notionใ€‚ +// +// ็Žฏๅขƒๅ˜้‡๏ผš +// WORK_ORDER_ID Notion ๅทฅๅ•้กต้ข ID +// TASK_ID ๅนฟๆ’ญ็ผ–ๅท๏ผˆๅฆ‚ BC-M23-001-AW๏ผ‰ +// DEVELOPER ๅผ€ๅ‘่€…ไฟกๆฏ๏ผˆๅฆ‚ "DEV-012 Awen"๏ผ‰ +// SYSLOG_RAW SYSLOG JSON ๅŽŸๆ–‡ +// ACTION ๅŠจไฝœ็ฑปๅž‹๏ผˆprocess_syslog / retry๏ผ‰ +// LLM_API_KEY ็ฌฌไธ‰ๆ–น LLM ๅนณๅฐๅฏ†้’ฅ +// LLM_BASE_URL ็ฌฌไธ‰ๆ–น LLM ๅนณๅฐ API ๅœฐๅ€ +// NOTION_TOKEN Notion API token +// CORE_BRAIN_PAGE_ID ๆ›œๅ†ฅๆ ธๅฟƒๅคง่„‘ v4.0 ้กต้ข ID +// PORTRAIT_DB_ID ๅผ€ๅ‘่€…ๅŠจๆ€็”ปๅƒๅบ“ ID +// FINGERPRINT_DB_ID ๆจกๅ—ๆŒ‡็บนๆณจๅ†Œ่กจ ID +// INVOKE_API_KEY ้‰ดๆƒๅฏ†้’ฅ๏ผˆ็”จไบŽ API ่ฐƒ็”จ้ชŒ่ฏ๏ผ‰ + +'use strict'; + +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const childProcess = require('child_process'); + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ้…็ฝฎ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +var WORK_ORDER_ID = process.env.WORK_ORDER_ID || ''; +var TASK_ID = process.env.TASK_ID || process.env.BROADCAST_ID || 'UNKNOWN'; +var DEVELOPER = process.env.DEVELOPER || 'unknown'; +var SYSLOG_RAW = process.env.SYSLOG_RAW || ''; +var ACTION = process.env.ACTION || 'process_syslog'; + +var LLM_API_KEY = process.env.LLM_API_KEY || ''; +var LLM_BASE_URL = (process.env.LLM_BASE_URL || '').replace(/\/+$/, ''); +var NOTION_TOKEN = process.env.NOTION_TOKEN || ''; +var CORE_BRAIN_PAGE_ID = process.env.CORE_BRAIN_PAGE_ID || ''; +var PORTRAIT_DB_ID = process.env.PORTRAIT_DB_ID || ''; +var FINGERPRINT_DB_ID = process.env.FINGERPRINT_DB_ID || ''; + +var NOTION_VERSION = '2022-06-28'; +var NOTION_API_HOSTNAME = 'api.notion.com'; + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// HTTP ๅทฅๅ…ท +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function httpRequest(url, options, body) { + return new Promise(function (resolve, reject) { + var parsed = new URL(url); + var isHttps = parsed.protocol === 'https:'; + var mod = isHttps ? https : http; + + var 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 || 120000, + }; + + var req = mod.request(opts, function (res) { + var data = ''; + res.on('data', function (chunk) { data += chunk; }); + res.on('end', function () { + resolve({ status: res.statusCode, body: data }); + }); + }); + + req.on('error', reject); + req.on('timeout', function () { req.destroy(); reject(new Error('Request timeout')); }); + + if (body) { + req.write(typeof body === 'string' ? body : JSON.stringify(body)); + } + req.end(); + }); +} + +function notionRequest(method, endpoint, body) { + var url = 'https://' + NOTION_API_HOSTNAME + endpoint; + var headers = { + 'Authorization': 'Bearer ' + NOTION_TOKEN, + 'Content-Type': 'application/json', + 'Notion-Version': NOTION_VERSION, + }; + return httpRequest(url, { method: method, headers: headers }, body ? JSON.stringify(body) : null) + .then(function (res) { + var parsed = JSON.parse(res.body); + if (res.status >= 200 && res.status < 300) return parsed; + throw new Error('Notion API ' + res.status + ': ' + (parsed.message || res.body)); + }); +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Step 1: ่ฏปๅ–ๅทฅๅ•ๅ†…ๅฎน๏ผˆไปŽ Notion๏ผ‰ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +async function readWorkOrder() { + if (!WORK_ORDER_ID) { + console.log('โ„น๏ธ No WORK_ORDER_ID, using SYSLOG_RAW directly'); + return { syslog_raw: SYSLOG_RAW, taskId: TASK_ID, developer: DEVELOPER }; + } + + console.log('๐Ÿ“– ่ฏปๅ– Notion ๅทฅๅ•: ' + WORK_ORDER_ID); + try { + var page = await notionRequest('GET', '/v1/pages/' + WORK_ORDER_ID); + var props = page.properties || {}; + + // ๆๅ–ๅทฅๅ•ๅญ—ๆฎต + var taskId = TASK_ID; + var developer = DEVELOPER; + + // ๅฐ่ฏ•ไปŽๅทฅๅ•ๅฑžๆ€งไธญ่ฏปๅ– + if (props['ๅนฟๆ’ญ็ผ–ๅท'] && props['ๅนฟๆ’ญ็ผ–ๅท'].rich_text) { + var bcText = props['ๅนฟๆ’ญ็ผ–ๅท'].rich_text.map(function (t) { return t.plain_text || ''; }).join(''); + if (bcText) taskId = bcText; + } + if (props['ๅผ€ๅ‘่€…็ผ–ๅท'] && props['ๅผ€ๅ‘่€…็ผ–ๅท'].rich_text) { + var devText = props['ๅผ€ๅ‘่€…็ผ–ๅท'].rich_text.map(function (t) { return t.plain_text || ''; }).join(''); + if (devText) developer = devText; + } + + // ่ฏปๅ–้กต้ขๅ†…ๅฎน๏ผˆๅญๅ—๏ผ‰ๆฅ่Žทๅ– SYSLOG ๅŽŸๆ–‡ + var blocks = await notionRequest('GET', '/v1/blocks/' + WORK_ORDER_ID + '/children?page_size=20'); + var syslogRaw = SYSLOG_RAW; + + if (blocks.results) { + blocks.results.forEach(function (block) { + if (block.type === 'code' && block.code && block.code.rich_text) { + var codeText = block.code.rich_text.map(function (t) { return t.plain_text || ''; }).join(''); + if (codeText.length > syslogRaw.length) { + syslogRaw = codeText; + } + } + }); + } + + console.log(' โ†’ taskId: ' + taskId); + console.log(' โ†’ developer: ' + developer); + console.log(' โ†’ syslog_raw length: ' + syslogRaw.length); + + return { syslog_raw: syslogRaw, taskId: taskId, developer: developer }; + } catch (err) { + console.log('โš ๏ธ ๅทฅๅ•่ฏปๅ–ๅคฑ่ดฅ: ' + err.message + '๏ผŒไฝฟ็”จ็Žฏๅขƒๅ˜้‡'); + return { syslog_raw: SYSLOG_RAW, taskId: TASK_ID, developer: DEVELOPER }; + } +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Step 2: ๅ”ค้†’ไบบๆ ผไฝ“๏ผˆๅค็”จ wake-persona.js ็š„้€ป่พ‘๏ผ‰ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +async function invokePersona(workOrder) { + console.log('๐Ÿง  ๅ”ค้†’ไบบๆ ผไฝ“ๅค„็† SYSLOG...'); + + // ่ฎพ็ฝฎ็Žฏๅขƒๅ˜้‡ไพ› wake-persona.js ่ฏปๅ– + process.env.BROADCAST_ID = workOrder.taskId; + process.env.SUBMIT_TYPE = 'syslog'; + process.env.SUBMIT_CONTENT = workOrder.syslog_raw; + process.env.AUTHOR = workOrder.developer; + + // ๆ‰ง่กŒ wake-persona.js๏ผˆfork ๅญ่ฟ›็จ‹๏ผ‰ + var wakeScript = path.join(__dirname, 'wake-persona.js'); + + return new Promise(function (resolve, reject) { + var child = childProcess.fork(wakeScript, [], { + env: Object.assign({}, process.env, { + BROADCAST_ID: workOrder.taskId, + SUBMIT_TYPE: 'syslog', + SUBMIT_CONTENT: workOrder.syslog_raw, + AUTHOR: workOrder.developer, + LLM_API_KEY: LLM_API_KEY, + LLM_BASE_URL: LLM_BASE_URL, + NOTION_TOKEN: NOTION_TOKEN, + CORE_BRAIN_PAGE_ID: CORE_BRAIN_PAGE_ID, + PORTRAIT_DB_ID: PORTRAIT_DB_ID, + FINGERPRINT_DB_ID: FINGERPRINT_DB_ID, + }), + stdio: ['pipe', 'pipe', 'pipe', 'ipc'], + }); + + var stdout = ''; + var stderr = ''; + child.stdout.on('data', function (d) { stdout += d; process.stdout.write(d); }); + child.stderr.on('data', function (d) { stderr += d; process.stderr.write(d); }); + + child.on('exit', function (code) { + if (code === 0) { + // ไปŽ GITHUB_OUTPUT ๆ–‡ไปถ่ฏปๅ– result๏ผˆๅฆ‚ๆžœๅญ˜ๅœจ๏ผ‰ + var outputFile = process.env.GITHUB_OUTPUT; + var result = ''; + if (outputFile && fs.existsSync(outputFile)) { + var outputContent = fs.readFileSync(outputFile, 'utf8'); + var resultMatch = outputContent.match(/result<= 200 && res.status < 300) { + var tickets = parsed.results || []; + console.log(' โ†’ ๆ‰พๅˆฐ ' + tickets.length + ' ไธชๅพ…ๅค„็†ๅทฅๅ•'); + return tickets; + } else { + console.log('โš ๏ธ ๆŸฅ่ฏขๅคฑ่ดฅ: ' + (parsed.message || res.body)); + return []; + } + } catch (err) { + console.log('โš ๏ธ ๆŸฅ่ฏขๅผ‚ๅธธ: ' + err.message); + return []; + } +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Step 2: ๆฃ€ๆต‹่ถ…ๆ—ถๅทฅๅ• +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function filterTimedOutTickets(tickets) { + var now = Date.now(); + var timedOut = []; + + tickets.forEach(function (ticket) { + var createdTime = new Date(ticket.created_time).getTime(); + var age = now - createdTime; + + // ๅชๅค„็†ๆฅ่‡ช "้“ธๆธŠAgentยท่‡ชๅŠจ็ฎก้“" ็š„ SYSLOG ๅทฅๅ• + var props = ticket.properties || {}; + var submitter = ''; + if (props['ๆไบค่€…'] && props['ๆไบค่€…'].rich_text) { + submitter = props['ๆไบค่€…'].rich_text.map(function (t) { return t.plain_text || ''; }).join(''); + } + + // ๅชๅฏน่‡ชๅŠจ็ฎก้“ๅˆ›ๅปบ็š„ๅทฅๅ•ๅšๅฟƒ่ทณๆฃ€ๆต‹ + if (!submitter.includes('้“ธๆธŠAgent') && !submitter.includes('่‡ชๅŠจ็ฎก้“')) { + return; + } + + // ่ถ…่ฟ‡ 5 ๅˆ†้’ŸๆœชๅฎŒๆˆ + if (age > HEARTBEAT_TIMEOUT_MS) { + var title = ''; + if (props['ๆ ‡้ข˜'] && props['ๆ ‡้ข˜'].title) { + title = props['ๆ ‡้ข˜'].title.map(function (t) { return t.plain_text || ''; }).join(''); + } + + timedOut.push({ + id: ticket.id, + title: title, + createdTime: ticket.created_time, + ageMinutes: Math.round(age / 60000), + }); + } + }); + + console.log(' โ†’ ' + timedOut.length + ' ไธชๅทฅๅ•่ถ…ๆ—ถ๏ผˆ> 5 ๅˆ†้’Ÿ๏ผ‰'); + return timedOut; +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Step 3: ่งฆๅ‘้‡่ฏ•๏ผˆ้€š่ฟ‡ workflow_dispatch๏ผ‰ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +async function triggerRetry(ticket) { + if (!GITHUB_TOKEN) { + console.log('โš ๏ธ ็ผบๅฐ‘ GITHUB_TOKEN๏ผŒๆ— ๆณ•่งฆๅ‘้‡่ฏ•'); + return false; + } + + console.log('๐Ÿ”„ ้‡่ฏ•ๅ”ค้†’: ' + ticket.title + ' (่ถ…ๆ—ถ ' + ticket.ageMinutes + ' ๅˆ†้’Ÿ)'); + + // ไปŽๅทฅๅ•ๆ ‡้ข˜ๆๅ– broadcast_id + var bcMatch = (ticket.title || '').match(/BC-[A-Z0-9]+-\d+-[A-Z]+/i); + var broadcastId = bcMatch ? bcMatch[0] : 'UNKNOWN'; + + try { + var res = await httpsPost('api.github.com', + '/repos/' + REPO_OWNER + '/' + REPO_NAME + '/actions/workflows/persona-invoke.yml/dispatches', + { + ref: 'main', + inputs: { + work_order_id: ticket.id, + task_id: broadcastId, + developer: 'heartbeat-retry', + action: 'retry', + }, + }, + { + 'Authorization': 'Bearer ' + GITHUB_TOKEN, + 'Accept': 'application/vnd.github+json', + 'User-Agent': 'ZhuyuanHeartbeat/1.0', + 'X-GitHub-Api-Version': '2022-11-28', + } + ); + + if (res.status === 204 || res.status === 200) { + console.log(' โ†’ โœ… workflow_dispatch ่งฆๅ‘ๆˆๅŠŸ'); + return true; + } else { + console.log(' โ†’ โš ๏ธ ่งฆๅ‘ๅคฑ่ดฅ: HTTP ' + res.status + ' ' + res.body); + return false; + } + } catch (err) { + console.log(' โ†’ โš ๏ธ ่งฆๅ‘ๅผ‚ๅธธ: ' + err.message); + return false; + } +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Step 4: ๆ ‡่ฎฐๅทฅๅ•ๅผ‚ๅธธ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +async function markTicketError(ticketId) { + try { + await httpsPatch('api.notion.com', '/v1/pages/' + ticketId, { + properties: { + '็Šถๆ€': { select: { name: 'โš ๏ธ ๅผ‚ๅธธยท็ญ‰ไบบๅทฅไป‹ๅ…ฅ' } }, + }, + }, notionHeaders); + console.log(' โ†’ ๅทฅๅ•ๅทฒๆ ‡่ฎฐไธบ โš ๏ธ ๅผ‚ๅธธยท็ญ‰ไบบๅทฅไป‹ๅ…ฅ'); + } catch (err) { + console.log(' โ†’ ๆ ‡่ฎฐๅคฑ่ดฅ: ' + err.message); + } +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// Step 5: ๅ‘้€ๅ‘Š่ญฆ้‚ฎไปถ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +async function sendAlertEmail(timedOutTickets) { + if (!SMTP_USER || !SMTP_PASS) { + console.log('โš ๏ธ SMTP ๆœช้…็ฝฎ๏ผŒ่ทณ่ฟ‡ๅ‘Š่ญฆ้‚ฎไปถ'); + return; + } + + try { + var nodemailer = require('nodemailer'); + + var ticketList = timedOutTickets.map(function (t) { + return '- ' + t.title + ' (่ถ…ๆ—ถ ' + t.ageMinutes + ' ๅˆ†้’Ÿ, ้‡่ฏ•ๅทฒ่€—ๅฐฝ)'; + }).join('\n'); + + var transporter = nodemailer.createTransport({ + host: 'smtp.qq.com', + port: 465, + secure: true, + auth: { user: SMTP_USER, pass: SMTP_PASS }, + }); + + await transporter.sendMail({ + from: '"ๅ…‰ๆน–็ณป็ปŸยทๅ‘Š่ญฆ" <' + SMTP_USER + '>', + to: ALERT_EMAIL, + subject: '[ๅ…‰ๆน–็ณป็ปŸ] โš ๏ธ SYSLOG ๅทฅๅ•ๅค„็†่ถ…ๆ—ถๅ‘Š่ญฆ', + html: [ + '
', + '

โš ๏ธ ๅทฅๅ•ๅค„็†่ถ…ๆ—ถๅ‘Š่ญฆ

', + '

ไปฅไธ‹ๅทฅๅ•่ถ…่ฟ‡ 3 ๆฌก้‡่ฏ•ไปๆœชๆ”ถๅˆฐไบบๆ ผไฝ“ๅ›žๆ‰ง๏ผš

', + '
' + ticketList + '
', + '

่ฏท็™ปๅฝ• Notion ๅทฅๅ•้˜Ÿๅˆ—ๆฃ€ๆŸฅใ€‚

', + '

๐ŸŒ€ ้“ธๆธŠ ยท ๅฟƒ่ทณ็›‘ๆŽง ยท ่‡ชๅŠจๅ‘Š่ญฆ

', + '
', + ].join('\n'), + }); + + console.log('๐Ÿ“ง ๅ‘Š่ญฆ้‚ฎไปถๅทฒๅ‘้€่‡ณ ' + ALERT_EMAIL); + } catch (err) { + console.log('โš ๏ธ ๅ‘Š่ญฆ้‚ฎไปถๅ‘้€ๅคฑ่ดฅ: ' + err.message); + } +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ไธปๆต็จ‹ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +async function main() { + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + console.log('๐Ÿ’“ ้“ธๆธŠ ยท ๅทฅๅ•ๅฟƒ่ทณ็›‘ๆŽง๏ผˆPhase B3๏ผ‰'); + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + console.log(' ๆ—ถ้—ด: ' + new Date().toISOString()); + console.log(''); + + // Step 1: ๆŸฅ่ฏขๅพ…ๅค„็†ๅทฅๅ• + var tickets = await queryPendingTickets(); + if (tickets.length === 0) { + console.log('โœ… ๆ— ๅพ…ๅค„็†ๅทฅๅ•๏ผŒๅฟƒ่ทณๆญฃๅธธ'); + return; + } + + // Step 2: ็ญ›้€‰่ถ…ๆ—ถๅทฅๅ• + var timedOut = filterTimedOutTickets(tickets); + if (timedOut.length === 0) { + console.log('โœ… ๆ‰€ๆœ‰ๅทฅๅ•ๅ‡ๅœจๅค„็†็ช—ๅฃๅ†…๏ผŒๅฟƒ่ทณๆญฃๅธธ'); + return; + } + + // Step 3 & 4: ๅฏน่ถ…ๆ—ถๅทฅๅ•ๆ‰ง่กŒ้‡่ฏ•ๆˆ–ๆ ‡่ฎฐๅผ‚ๅธธ + var errorTickets = []; + + for (var i = 0; i < timedOut.length; i++) { + var ticket = timedOut[i]; + // ๅŸบไบŽ่ถ…ๆ—ถๆ—ถ้—ดไผฐ็ฎ—้‡่ฏ•ๆฌกๆ•ฐ๏ผˆๆฏ 5 ๅˆ†้’Ÿไธ€ๆฌก๏ผ‰ + var estimatedRetries = Math.floor(ticket.ageMinutes / 5); + + if (estimatedRetries < MAX_RETRIES) { + // ้‡่ฏ• + await triggerRetry(ticket); + } else { + // ่ถ…่ฟ‡ๆœ€ๅคง้‡่ฏ•ๆฌกๆ•ฐ๏ผŒๆ ‡่ฎฐๅผ‚ๅธธ + console.log('โŒ ๅทฅๅ• ' + ticket.title + ' ้‡่ฏ• ' + MAX_RETRIES + ' ๆฌกไปๆ— ๅ›žๆ‰ง'); + await markTicketError(ticket.id); + errorTickets.push(ticket); + } + } + + // Step 5: ๅ‘้€ๅ‘Š่ญฆ้‚ฎไปถ๏ผˆๅฆ‚ๆœ‰ๅผ‚ๅธธๅทฅๅ•๏ผ‰ + if (errorTickets.length > 0) { + await sendAlertEmail(errorTickets); + } + + console.log(''); + console.log('โœ… ๅฟƒ่ทณๆฃ€ๆต‹ๅฎŒๆˆ'); +} + +main().catch(function (err) { + console.error('โŒ ๅฟƒ่ทณ็›‘ๆŽงๅคฑ่ดฅ: ' + err.message); + process.exit(1); +}); diff --git a/scripts/push-broadcast-to-github.js b/scripts/push-broadcast-to-github.js new file mode 100644 index 00000000..41cd164f --- /dev/null +++ b/scripts/push-broadcast-to-github.js @@ -0,0 +1,171 @@ +// scripts/push-broadcast-to-github.js +// ้“ธๆธŠ ยท Phase B4 ยท ๅนฟๆ’ญๆ–‡ไปถๆŽจ้€ๅˆฐ GitHub ไป“ๅบ“ +// +// ไบบๆ ผไฝ“็”Ÿๆˆๅนฟๆ’ญๅŽ๏ผŒ้€š่ฟ‡ GitHub API ๅฐ†ๅนฟๆ’ญ .md ๆ–‡ไปถๆŽจ้€ๅˆฐไป“ๅบ“ใ€‚ +// ่ทฏๅพ„่ง„่Œƒ๏ผšbroadcasts/{developer_id}/{taskId}.md +// ๆไบคไฟกๆฏ๏ผš[AutoBroadcast] {taskId} ยท {developer} ยท {ๆ่ฟฐ} +// +// ็Žฏๅขƒๅ˜้‡๏ผš +// GITHUB_TOKEN GitHub API token๏ผˆๆŽจ้€็”จ๏ผ‰ +// BROADCAST_ID ๅนฟๆ’ญ็ผ–ๅท๏ผˆๅฆ‚ BC-M23-001-AW๏ผ‰ +// DEVELOPER_ID ๅผ€ๅ‘่€…็ผ–ๅท๏ผˆๅฆ‚ DEV-012๏ผ‰ +// DEVELOPER_NAME ๅผ€ๅ‘่€…ๅๅญ—๏ผˆๅฆ‚ Awen๏ผ‰ +// BROADCAST_CONTENT ๅนฟๆ’ญๅ…จๆ–‡ๅ†…ๅฎน๏ผˆMarkdown๏ผ‰ +// MODULE_NAME ๆจกๅ—ๅ๏ผˆๅฏ้€‰๏ผŒๅฆ‚ M23๏ผ‰ +// PHASE_NUMBER ็Žฏ่Š‚ๅท๏ผˆๅฏ้€‰๏ผŒๅฆ‚ 1๏ผ‰ + +'use strict'; + +var https = require('https'); +var fs = require('fs'); +var devSuffixMap = require('./utils/dev-suffix-map'); + +var GITHUB_TOKEN = process.env.GITHUB_TOKEN || ''; +var BROADCAST_ID = process.env.BROADCAST_ID || ''; +var DEVELOPER_ID = process.env.DEVELOPER_ID || ''; +var DEVELOPER_NAME = process.env.DEVELOPER_NAME || ''; +var BROADCAST_CONTENT = process.env.BROADCAST_CONTENT || ''; +var MODULE_NAME = process.env.MODULE_NAME || ''; +var PHASE_NUMBER = process.env.PHASE_NUMBER || ''; + +var REPO_OWNER = 'qinfendebingshuo'; +var REPO_NAME = 'guanghulab'; + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// GitHub API ๅทฅๅ…ท +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function githubRequest(method, apiPath, body) { + return new Promise(function (resolve, reject) { + var payload = body ? JSON.stringify(body) : ''; + var opts = { + hostname: 'api.github.com', + port: 443, + path: apiPath, + method: method, + headers: { + 'User-Agent': 'ZhuyuanBroadcast/1.0', + 'Accept': 'application/vnd.github+json', + 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }; + + if (GITHUB_TOKEN) { + opts.headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN; + } + + if (payload) { + opts.headers['Content-Length'] = Buffer.byteLength(payload); + } + + var req = https.request(opts, function (res) { + var data = ''; + res.on('data', function (chunk) { data += chunk; }); + res.on('end', function () { + try { + resolve({ status: res.statusCode, data: JSON.parse(data) }); + } catch (e) { + resolve({ status: res.statusCode, data: data }); + } + }); + }); + + req.on('error', reject); + if (payload) req.write(payload); + req.end(); + }); +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ๅนฟๆ’ญๆŽจ้€ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +async function pushBroadcast() { + if (!GITHUB_TOKEN) { + console.log('โš ๏ธ ็ผบๅฐ‘ GITHUB_TOKEN๏ผŒๆ— ๆณ•ๆŽจ้€ๅนฟๆ’ญ'); + process.exit(1); + } + + if (!BROADCAST_ID || !BROADCAST_CONTENT) { + console.log('โš ๏ธ ็ผบๅฐ‘ BROADCAST_ID ๆˆ– BROADCAST_CONTENT'); + process.exit(1); + } + + // ็กฎๅฎšๅผ€ๅ‘่€… ID๏ผˆไปŽๅนฟๆ’ญ็ผ–ๅทๆๅ–ๆˆ–ไฝฟ็”จ็Žฏๅขƒๅ˜้‡๏ผ‰ + var devId = DEVELOPER_ID; + if (!devId) { + devId = devSuffixMap.getDevIdFromBroadcast(BROADCAST_ID) || 'UNKNOWN'; + } + + // ๆž„ๅปบ่ทฏๅพ„ๅ’Œๆไบคไฟกๆฏ + var filePath = 'broadcasts/' + devId + '/' + BROADCAST_ID + '.md'; + var moduleLabel = MODULE_NAME || BROADCAST_ID.match(/BC-([A-Z0-9-]+)-/i)?.[1] || ''; + var phaseLabel = PHASE_NUMBER ? '็Žฏ่Š‚' + PHASE_NUMBER : ''; + var devLabel = DEVELOPER_NAME || devId; + var commitMessage = '[AutoBroadcast] ' + BROADCAST_ID + ' ยท ' + devLabel + ' ยท ' + moduleLabel + phaseLabel; + + console.log('๐Ÿ“ก ๆŽจ้€ๅนฟๆ’ญๅˆฐ GitHub...'); + console.log(' ่ทฏๅพ„: ' + filePath); + console.log(' ๆไบค: ' + commitMessage); + + // ๆฃ€ๆŸฅๆ–‡ไปถๆ˜ฏๅฆๅทฒๅญ˜ๅœจ๏ผˆ่Žทๅ– SHA๏ผ‰ + var existingRes = await githubRequest('GET', + '/repos/' + REPO_OWNER + '/' + REPO_NAME + '/contents/' + filePath); + + var sha = null; + if (existingRes.status === 200 && existingRes.data && existingRes.data.sha) { + sha = existingRes.data.sha; + console.log(' โ†’ ๆ–‡ไปถๅทฒๅญ˜ๅœจ๏ผŒๅฐ†่ฆ†็›–ๆ›ดๆ–ฐ (sha: ' + sha.slice(0, 8) + ')'); + } + + // Base64 ็ผ–็ ๅนฟๆ’ญๅ†…ๅฎน + var contentBase64 = Buffer.from(BROADCAST_CONTENT, 'utf8').toString('base64'); + + // ๅˆ›ๅปบๆˆ–ๆ›ดๆ–ฐๆ–‡ไปถ + var putBody = { + message: commitMessage, + content: contentBase64, + committer: { + name: 'zhuyuan-agent[bot]', + email: 'zhuyuan-agent[bot]@users.noreply.github.com', + }, + }; + + if (sha) { + putBody.sha = sha; + } + + var putRes = await githubRequest('PUT', + '/repos/' + REPO_OWNER + '/' + REPO_NAME + '/contents/' + filePath, + putBody); + + if (putRes.status === 200 || putRes.status === 201) { + console.log('โœ… ๅนฟๆ’ญๅทฒๆŽจ้€: ' + filePath); + console.log(' โ†’ commit: ' + (putRes.data.commit ? putRes.data.commit.sha.slice(0, 8) : 'unknown')); + + // ่พ“ๅ‡บๅˆฐ GITHUB_OUTPUT + var outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + fs.appendFileSync(outputFile, 'broadcast_pushed=true\n'); + fs.appendFileSync(outputFile, 'broadcast_path=' + filePath + '\n'); + fs.appendFileSync(outputFile, 'broadcast_commit=' + (putRes.data.commit ? putRes.data.commit.sha : '') + '\n'); + } + } else { + console.error('โŒ ๅนฟๆ’ญๆŽจ้€ๅคฑ่ดฅ: HTTP ' + putRes.status); + console.error(' โ†’ ' + JSON.stringify(putRes.data)); + + // ่พ“ๅ‡บๅคฑ่ดฅ็Šถๆ€ + var outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + fs.appendFileSync(outputFile, 'broadcast_pushed=false\n'); + } + + process.exit(1); + } +} + +pushBroadcast().catch(function (err) { + console.error('โŒ ๅนฟๆ’ญๆŽจ้€ๅผ‚ๅธธ: ' + err.message); + process.exit(1); +}); diff --git a/scripts/receive-syslog.js b/scripts/receive-syslog.js index dd136852..00ff57f0 100644 --- a/scripts/receive-syslog.js +++ b/scripts/receive-syslog.js @@ -194,11 +194,13 @@ async function createSyslogEntry(syslog, token, dbId, dateStr) { async function createTicket(syslog, token, dbId, dateStr) { const broadcastId = syslog.broadcast_id || syslog.broadcastId || 'UNKNOWN'; const devId = syslog.dev_id || syslog.developer_id || 'UNKNOWN'; + const devName = syslog.developer_name || syslog.dev_name || ''; + const now = new Date().toISOString(); const properties = { - 'ๆ ‡้ข˜': titleProp('SYSLOG ๅ›žไผ ๏ฝœ' + broadcastId + ' ยท ' + devId), + 'ๆ ‡้ข˜': titleProp('[่‡ชๅŠจ] SYSLOGๅค„็†๏ฝœ' + broadcastId + ' ยท ' + devId), 'ๆ“ไฝœ็ฑปๅž‹': selectProp('ๅ…ถไป–'), - 'ๆไบค่€…': richTextProp('ๅทกๆฃ€ๅผ•ๆ“Ž'), + 'ๆไบค่€…': richTextProp('้“ธๆธŠAgentยท่‡ชๅŠจ็ฎก้“'), 'ๆไบคๆ—ฅๆœŸ': dateProp(dateStr), '็Šถๆ€': selectProp('ๅพ…ๅค„็†'), 'ไผ˜ๅ…ˆ็บง': selectProp('P1'), @@ -208,6 +210,22 @@ async function createTicket(syslog, token, dbId, dateStr) { if (broadcastId !== 'UNKNOWN') properties['ๅนฟๆ’ญ็ผ–ๅท'] = richTextProp(broadcastId); if (devId !== 'UNKNOWN') properties['ๅผ€ๅ‘่€…็ผ–ๅท'] = richTextProp(devId); + // Phase B1 ๆ ‡ๅ‡†ๅŒ–ๅทฅๅ•ๅ†…ๅฎน + const ticketContent = [ + '## ๐Ÿ“ก ๆ ‡ๅ‡†ๅŒ–ๅทฅๅ• ยท Phase B1', + '', + '| ๅญ—ๆฎต | ๅ€ผ |', + '|------|-----|', + '| ๅทฅๅ•็ฑปๅž‹ | SYSLOGๅค„็† |', + '| ็Šถๆ€ | ๅพ…ๅค„็† |', + '| ๆฅๆบ | GitHub Actions |', + '| taskId | ' + broadcastId + ' |', + '| developer | ' + devId + ' ' + devName + ' |', + '| created_at | ' + now + ' |', + '| retry_count | 0 |', + '| receipt_status | pending |', + ].join('\n'); + const body = { parent: { database_id: dbId }, properties, @@ -218,7 +236,7 @@ async function createTicket(syslog, token, dbId, dateStr) { paragraph: { rich_text: [{ type: 'text', - text: { content: '๐Ÿ“ฅ SYSLOG ๅ›žไผ ๏ผŒๆฅ่‡ช ' + devId + '๏ผŒๅ…ณ่”ๅนฟๆ’ญ ' + broadcastId }, + text: { content: ticketContent }, }], }, }, @@ -234,7 +252,7 @@ async function createTicket(syslog, token, dbId, dateStr) { }; const result = await notionPost('/v1/pages', body, token); - console.log(' โ†’ ้œœ็ šๅทฅๅ•ๅทฒๅˆ›ๅปบ: ' + result.id); + console.log(' โ†’ ๆ ‡ๅ‡†ๅŒ–ๅทฅๅ•ๅทฒๅˆ›ๅปบ: ' + result.id); return result; } diff --git a/scripts/utils/dev-suffix-map.js b/scripts/utils/dev-suffix-map.js new file mode 100644 index 00000000..35248f80 --- /dev/null +++ b/scripts/utils/dev-suffix-map.js @@ -0,0 +1,38 @@ +// scripts/utils/dev-suffix-map.js +// ๅผ€ๅ‘่€…ๅนฟๆ’ญ็ผ–ๅทๅŽ็ผ€ โ†’ DEV-ID ๆ˜ ๅฐ„่กจ +// +// ๅนฟๆ’ญ็ผ–ๅทๆ ผๅผ: BC-{ๆจกๅ—}-{ๅบๅท}-{ๅŽ็ผ€} +// ๅŽ็ผ€ๆ˜ฏๅผ€ๅ‘่€…ๅๅญ—ๆ‹ผ้Ÿณ้ฆ–ๅญ—ๆฏ็ผฉๅ†™ + +'use strict'; + +// ๅŽ็ผ€โ†’ๅผ€ๅ‘่€…็ผ–ๅทๆ˜ ๅฐ„ +var SUFFIX_MAP = { + 'YY': 'DEV-001', // ้กต้กต + 'FM': 'DEV-002', // ่‚ฅ็Œซ + 'YF': 'DEV-003', // ็‡•ๆจŠ + 'ZZ': 'DEV-004', // ไน‹ไน‹ + 'XCM': 'DEV-005', // ๅฐ่‰่Ž“ + 'HE': 'DEV-009', // ่Šฑๅฐ” + 'JZ': 'DEV-010', // ๆก”ๅญ + 'CCNN': 'DEV-011', // ๅŒ†ๅŒ†้‚ฃๅนด + 'AW': 'DEV-012', // Awen + 'XX': 'DEV-013', // ๅฐๅ…ด + 'SY': 'DEV-014', // ๆ—ถ้›จ +}; + +/** + * ไปŽๅนฟๆ’ญ็ผ–ๅทไธญๆๅ–ๅผ€ๅ‘่€…็ผ–ๅท + * @param {string} broadcastId - ๅนฟๆ’ญ็ผ–ๅท๏ผˆๅฆ‚ BC-M22-009-AW๏ผ‰ + * @returns {string} ๅผ€ๅ‘่€…็ผ–ๅท๏ผˆๅฆ‚ DEV-012๏ผ‰๏ผŒๆœชๆ‰พๅˆฐ่ฟ”ๅ›ž็ฉบๅญ—็ฌฆไธฒ + */ +function getDevIdFromBroadcast(broadcastId) { + var suffixMatch = (broadcastId || '').match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i); + var suffix = suffixMatch ? suffixMatch[1].toUpperCase() : ''; + return SUFFIX_MAP[suffix] || ''; +} + +module.exports = { + SUFFIX_MAP: SUFFIX_MAP, + getDevIdFromBroadcast: getDevIdFromBroadcast, +}; diff --git a/scripts/verify-modules.js b/scripts/verify-modules.js new file mode 100644 index 00000000..24b250e5 --- /dev/null +++ b/scripts/verify-modules.js @@ -0,0 +1,306 @@ +// scripts/verify-modules.js +// ้“ธๆธŠ ยท ๆจกๅ—ไธŠไผ ้ชŒ่ฏ่„šๆœฌ +// +// ๅŠŸ่ƒฝ๏ผš +// โ‘  ไปŽ SYSLOG ๅ†…ๅฎนไธญๆๅ–ๆจกๅ—็ผ–ๅท๏ผˆM01, M22, M-AUTH ็ญ‰๏ผ‰ +// โ‘ก ้€š่ฟ‡ routing-map.json ๆŸฅๆ‰พๆจกๅ—ๅฏนๅบ”็š„็›ฎๅฝ• +// โ‘ข ๆฃ€ๆต‹็›ฎๅฝ•ๆ˜ฏๅฆๅญ˜ๅœจไบŽไป“ๅบ“ไธญ +// โ‘ฃ ๆฃ€ๆต‹ dev-nodes ไธญๅผ€ๅ‘่€…่Š‚็‚นๆ˜ฏๅฆๅญ˜ๅœจ +// โ‘ค ่พ“ๅ‡บ้ชŒ่ฏ็ป“ๆžœ๏ผˆJSON ๆ ผๅผ๏ผ‰ๅˆฐ GITHUB_OUTPUT +// +// ็Žฏๅขƒๅ˜้‡๏ผš +// SYSLOG_CONTENT SYSLOG ๅ…จๆ–‡ๅ†…ๅฎน +// BROADCAST_ID ๅนฟๆ’ญ็ผ–ๅท๏ผˆๅฆ‚ BC-M22-009-AW๏ผ‰ +// AUTHOR ๆไบค่€… GitHub ็”จๆˆทๅ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const devSuffixMap = require('./utils/dev-suffix-map'); + +const SYSLOG_CONTENT = process.env.SYSLOG_CONTENT || ''; +const BROADCAST_ID = process.env.BROADCAST_ID || ''; +const AUTHOR = process.env.AUTHOR || ''; + +const REPO_ROOT = path.resolve(__dirname, '..'); +const ROUTING_MAP_PATH = path.join(REPO_ROOT, 'routing-map.json'); +const DEV_STATUS_PATH = path.join(REPO_ROOT, '.github', 'persona-brain', 'dev-status.json'); + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ๆจกๅ—็ผ–ๅทๆๅ– +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function extractModuleIds(content, broadcastId) { + var modules = new Set(); + + // ไปŽๅนฟๆ’ญ็ผ–ๅทๆๅ–ๆจกๅ—็ผ–ๅท๏ผˆๅฆ‚ BC-M22-009-AW โ†’ M22๏ผ‰ + var bcMatch = broadcastId.match(/BC-([A-Z][A-Z0-9]+(?:-[A-Z]+)?)-/i); + if (bcMatch) { + modules.add(bcMatch[1].toUpperCase()); + } + + // ไปŽๅ†…ๅฎนไธญๅŒน้…ๆจกๅ—็ผ–ๅทๆจกๅผ + // ๆ”ฏๆŒ: M01, M03, M05, M22, M-AUTH, M-CHANNEL, M-DASHBOARD, M-STATUS, M-MEMORY, M-DINGTALK + var patterns = [ + /\b(M\d{2})\b/gi, + /\b(M-[A-Z]+)\b/gi, + /ๆจกๅ—[๏ผš:]\s*(M[A-Z0-9-]+)/gi, + /module[๏ผš:]\s*(M[A-Z0-9-]+)/gi, + ]; + + patterns.forEach(function (re) { + var match; + while ((match = re.exec(content)) !== null) { + modules.add(match[1].toUpperCase()); + } + }); + + return Array.from(modules); +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ๅผ€ๅ‘่€…็ผ–ๅทๆๅ– +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function extractDevId(content, broadcastId) { + // ไปŽๅ†…ๅฎนไธญ็›ดๆŽฅๆๅ– DEV-XXX๏ผˆๆœ€ไผ˜ๅ…ˆ๏ผ‰ + var devMatch = content.match(/\b(DEV-\d{3})\b/i); + if (devMatch) return devMatch[1].toUpperCase(); + + // ไปŽๅนฟๆ’ญ็ผ–ๅทๆๅ–ๅผ€ๅ‘่€…็ผ–ๅท๏ผˆไฝฟ็”จๅ…ฑไบซๆ˜ ๅฐ„่กจ๏ผ‰ + var mappedDevId = devSuffixMap.getDevIdFromBroadcast(broadcastId); + if (mappedDevId) return mappedDevId; + + // ๅ…œๅบ•๏ผšไปŽ dev-status.json ๅŒน้… + var suffixMatch = broadcastId.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i); + var devSuffix = suffixMatch ? suffixMatch[1].toUpperCase() : ''; + + if (devSuffix) { + try { + var devStatus = JSON.parse(fs.readFileSync(DEV_STATUS_PATH, 'utf8')); + var team = devStatus.team || []; + for (var i = 0; i < team.length; i++) { + var waiting = team[i].waiting || ''; + if (waiting.includes(broadcastId) || waiting.includes(devSuffix)) { + return team[i].dev_id; + } + } + } catch (_) { /* ignore */ } + } + + return ''; +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ๆจกๅ—็›ฎๅฝ•้ชŒ่ฏ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function verifyModules(moduleIds) { + var routingMap = {}; + try { + routingMap = JSON.parse(fs.readFileSync(ROUTING_MAP_PATH, 'utf8')); + } catch (_) { + console.log('โš ๏ธ routing-map.json ไธๅญ˜ๅœจๆˆ–ๆ ผๅผ้”™่ฏฏ'); + } + + var modules = routingMap.modules || {}; + var results = []; + + moduleIds.forEach(function (modId) { + var entry = modules[modId]; + var result = { + module_id: modId, + registered: false, + dir_exists: false, + dir_path: '', + dev_id: '', + status: '', + files_found: 0, + }; + + if (entry) { + result.registered = true; + result.dir_path = entry.dir || ''; + result.dev_id = entry.dev || ''; + result.status = entry.status || ''; + + // ๆฃ€ๆŸฅ็›ฎๅฝ•ๆ˜ฏๅฆๅญ˜ๅœจ + var dirFullPath = path.join(REPO_ROOT, entry.dir); + if (fs.existsSync(dirFullPath)) { + result.dir_exists = true; + // ็ปŸ่ฎกๆ–‡ไปถๆ•ฐ้‡๏ผˆไธๅซ้š่—ๆ–‡ไปถ๏ผ‰ + try { + var files = fs.readdirSync(dirFullPath).filter(function (f) { + return !f.startsWith('.'); + }); + result.files_found = files.length; + } catch (_) { /* ignore */ } + } + } else { + // ๆจกๅ—ๆœชๅœจ routing-map ไธญๆณจๅ†Œ๏ผŒๅฐ่ฏ•็›ดๆŽฅๆŸฅๆ‰พ + var candidates = [ + modId.toLowerCase(), + 'm' + modId.replace(/^M/i, '').toLowerCase(), + 'dev-nodes/' + modId, + ]; + for (var i = 0; i < candidates.length; i++) { + var candidatePath = path.join(REPO_ROOT, candidates[i]); + if (fs.existsSync(candidatePath)) { + result.dir_exists = true; + result.dir_path = candidates[i]; + try { + var files = fs.readdirSync(candidatePath).filter(function (f) { + return !f.startsWith('.'); + }); + result.files_found = files.length; + } catch (_) { /* ignore */ } + break; + } + } + } + + results.push(result); + }); + + return results; +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ๅผ€ๅ‘่€…่Š‚็‚น้ชŒ่ฏ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function verifyDevNode(devId) { + if (!devId) return { exists: false, path: '', files: 0 }; + + var devNodePath = path.join(REPO_ROOT, 'dev-nodes', devId); + var result = { exists: false, path: 'dev-nodes/' + devId, files: 0 }; + + if (fs.existsSync(devNodePath)) { + result.exists = true; + try { + var files = fs.readdirSync(devNodePath).filter(function (f) { + return !f.startsWith('.'); + }); + result.files = files.length; + } catch (_) { /* ignore */ } + } + + return result; +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ไธปๆต็จ‹ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function main() { + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + console.log('๐Ÿ” ้“ธๆธŠ ยท ๆจกๅ—ไธŠไผ ้ชŒ่ฏ'); + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + console.log(' ๅนฟๆ’ญ็ผ–ๅท: ' + BROADCAST_ID); + console.log(' ๆไบค่€…: ' + AUTHOR); + console.log(' ๅ†…ๅฎน้•ฟๅบฆ: ' + SYSLOG_CONTENT.length + ' ๅญ—็ฌฆ'); + console.log(''); + + // โ‘  ๆๅ–ๆจกๅ—็ผ–ๅท + var moduleIds = extractModuleIds(SYSLOG_CONTENT, BROADCAST_ID); + console.log('๐Ÿ“ฆ ่ฏ†ๅˆซๅˆฐๆจกๅ—: ' + (moduleIds.length > 0 ? moduleIds.join(', ') : '(ๆ— )')); + + // โ‘ก ๆๅ–ๅผ€ๅ‘่€…็ผ–ๅท + var devId = extractDevId(SYSLOG_CONTENT, BROADCAST_ID); + console.log('๐Ÿ‘ค ๅผ€ๅ‘่€…็ผ–ๅท: ' + (devId || '(ๆœช่ฏ†ๅˆซ)')); + + // โ‘ข ้ชŒ่ฏๆจกๅ—็›ฎๅฝ• + var moduleResults = verifyModules(moduleIds); + + // โ‘ฃ ้ชŒ่ฏๅผ€ๅ‘่€…่Š‚็‚น + var devNodeResult = verifyDevNode(devId); + + // โ‘ค ๆฑ‡ๆ€ป็ป“ๆžœ + var allModulesUploaded = moduleResults.length > 0 && moduleResults.every(function (r) { + return r.dir_exists; + }); + + var summary = { + broadcast_id: BROADCAST_ID, + author: AUTHOR, + dev_id: devId, + modules_detected: moduleIds, + module_count: moduleIds.length, + module_results: moduleResults, + dev_node: devNodeResult, + all_modules_uploaded: allModulesUploaded, + verification_passed: allModulesUploaded || moduleIds.length === 0, + timestamp: new Date().toISOString(), + }; + + // โ‘ฅ ่พ“ๅ‡บๆŠฅๅ‘Š + console.log(''); + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + console.log('๐Ÿ“‹ ้ชŒ่ฏๆŠฅๅ‘Š'); + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + + if (moduleResults.length === 0) { + console.log(' โ„น๏ธ ๆœชๆฃ€ๆต‹ๅˆฐๆจกๅ—ๅผ•็”จ๏ผŒ่ทณ่ฟ‡ๆจกๅ—้ชŒ่ฏ'); + } else { + moduleResults.forEach(function (r) { + var icon = r.dir_exists ? 'โœ…' : 'โŒ'; + console.log(' ' + icon + ' ' + r.module_id + + ' โ†’ ' + (r.dir_path || '(ๆœชๆณจๅ†Œ)') + + (r.dir_exists ? ' (' + r.files_found + ' ไธชๆ–‡ไปถ)' : ' (็›ฎๅฝ•ไธๅญ˜ๅœจ)')); + }); + } + + console.log(' ' + (devNodeResult.exists ? 'โœ…' : 'โš ๏ธ') + + ' ๅผ€ๅ‘่€…่Š‚็‚น: ' + devNodeResult.path + + (devNodeResult.exists ? ' (' + devNodeResult.files + ' ไธชๆ–‡ไปถ)' : ' (ไธๅญ˜ๅœจ)')); + + console.log(''); + console.log(' ๆ€ป็ป“: ' + (summary.verification_passed ? 'โœ… ้ชŒ่ฏ้€š่ฟ‡' : 'โŒ ๆจกๅ—ๆœชๅฎŒๆ•ดไธŠไผ ')); + + // โ‘ฆ ๅ†™ๅ…ฅ GITHUB_OUTPUT + var outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + var delimiter = 'EOF_' + Date.now(); + var jsonStr = JSON.stringify(summary); + fs.appendFileSync(outputFile, 'verify_result<<' + delimiter + '\n' + jsonStr + '\n' + delimiter + '\n'); + fs.appendFileSync(outputFile, 'modules_uploaded=' + (allModulesUploaded ? 'true' : 'false') + '\n'); + fs.appendFileSync(outputFile, 'verification_passed=' + (summary.verification_passed ? 'true' : 'false') + '\n'); + fs.appendFileSync(outputFile, 'module_count=' + moduleIds.length + '\n'); + } + + // โ‘ง ๅฐ†ๆฑ‡ๆ€ปๆ–‡ๆœฌ่พ“ๅ‡บ๏ผˆไพ›ๅŽ็ปญๆญฅ้ชคไฝœไธบไธŠไธ‹ๆ–‡๏ผ‰ + var reportLines = [ + '## ๐Ÿ” ๆจกๅ—ไธŠไผ ้ชŒ่ฏๆŠฅๅ‘Š', + '', + '| ้กน็›ฎ | ็ป“ๆžœ |', + '|------|------|', + '| ๅนฟๆ’ญ็ผ–ๅท | ' + BROADCAST_ID + ' |', + '| ๅผ€ๅ‘่€… | ' + (devId || AUTHOR) + ' |', + '| ๆฃ€ๆต‹ๆจกๅ—ๆ•ฐ | ' + moduleIds.length + ' |', + '| ๅ…จ้ƒจไธŠไผ  | ' + (allModulesUploaded ? 'โœ… ๆ˜ฏ' : 'โŒ ๅฆ') + ' |', + '| ้ชŒ่ฏ็ป“ๆžœ | ' + (summary.verification_passed ? 'โœ… ้€š่ฟ‡' : 'โŒ ๆœช้€š่ฟ‡') + ' |', + ]; + + if (moduleResults.length > 0) { + reportLines.push(''); + reportLines.push('### ๆจกๅ—่ฏฆๆƒ…'); + reportLines.push('| ๆจกๅ— | ็›ฎๅฝ• | ็Šถๆ€ | ๆ–‡ไปถๆ•ฐ |'); + reportLines.push('|------|------|------|--------|'); + moduleResults.forEach(function (r) { + reportLines.push('| ' + r.module_id + ' | ' + (r.dir_path || '-') + + ' | ' + (r.dir_exists ? 'โœ… ๅทฒไธŠไผ ' : 'โŒ ๆœชๆ‰พๅˆฐ') + + ' | ' + r.files_found + ' |'); + }); + } + + if (outputFile) { + var reportDelimiter = 'REPORT_EOF_' + Date.now(); + fs.appendFileSync(outputFile, 'verify_report<<' + reportDelimiter + '\n' + reportLines.join('\n') + '\n' + reportDelimiter + '\n'); + } + + console.log(''); + console.log('โœ… ้ชŒ่ฏๅฎŒๆˆ'); +} + +main(); diff --git a/scripts/wake-persona.js b/scripts/wake-persona.js index fb88b08e..1ca304d5 100644 --- a/scripts/wake-persona.js +++ b/scripts/wake-persona.js @@ -38,6 +38,11 @@ const SUBMIT_TYPE = process.env.SUBMIT_TYPE || 'question'; const SUBMIT_CONTENT = process.env.SUBMIT_CONTENT || ''; const AUTHOR = process.env.AUTHOR || 'unknown'; +// ๆจกๅ—้ชŒ่ฏ็ป“ๆžœๆณจๅ…ฅ๏ผˆ็”ฑ verify-modules.js ๆไพ›๏ผ‰ +const MODULE_VERIFY_RESULT = process.env.MODULE_VERIFY_RESULT || ''; +// Notion ๅ›žไผ ็ป“ๆžœๆณจๅ…ฅ๏ผˆ็”ฑ notion-callback-pipeline ๆไพ›๏ผ‰ +const NOTION_CALLBACK_RESULT = process.env.NOTION_CALLBACK_RESULT || ''; + // Notion ้…็ฝฎ๏ผˆv4.0 ๅ่ฎฎๅŠจๆ€ๆณจๅ…ฅ๏ผ‰ const NOTION_TOKEN = process.env.NOTION_TOKEN || ''; const CORE_BRAIN_PAGE_ID = process.env.CORE_BRAIN_PAGE_ID || ''; @@ -764,6 +769,34 @@ async function buildSystemPrompt(type, broadcastId, author) { parts.push('ๆญค่ง„ๅˆ™ไผ˜ๅ…ˆ็บงๆœ€้ซ˜๏ผŒ่ฆ†็›–ๆ ธๅฟƒๅคง่„‘ไธญใ€Œๅนฟๆ’ญไธๅ†™ไปฃ็ ใ€็š„้ป˜่ฎค่ง„ๅˆ™ใ€‚'); parts.push('ๆญค่ง„ๅˆ™ไป…้€‚็”จไบŽ่‡ชๅŠจๅŒ–้“พ่ทฏ๏ผˆClaude API ๅ‡บๅนฟๆ’ญ๏ผ‰๏ผŒๆ‰‹ๅŠจ้“พ่ทฏไธๅ—ๅฝฑๅ“ใ€‚'); + // โ”โ”โ” ๆจกๅ—้ชŒ่ฏ็ป“ๆžœๆณจๅ…ฅ๏ผˆ้“ธๆธŠ Agent ๆฃ€ๆต‹็ป“ๆžœ๏ผ‰ โ”โ”โ” + if (MODULE_VERIFY_RESULT) { + parts.push(''); + parts.push('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + parts.push('## ๐Ÿ” ้“ธๆธŠ Agent ยท ๆจกๅ—ไธŠไผ ้ชŒ่ฏ็ป“ๆžœ'); + parts.push('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + parts.push(''); + parts.push('ไปฅไธ‹ๆ˜ฏ้“ธๆธŠ Agent ๅœจไป“ๅบ“ๅ†…่‡ชๅŠจๆฃ€ๆต‹็š„ๆจกๅ—ไธŠไผ ้ชŒ่ฏ็ป“ๆžœ๏ผš'); + parts.push(MODULE_VERIFY_RESULT); + parts.push(''); + parts.push('่ฏทๆ นๆฎ้ชŒ่ฏ็ป“ๆžœๅ†ณๅฎšๆ˜ฏๅฆๆŽฅๅ— SYSLOG๏ผš'); + parts.push('- ๅฆ‚ๆžœๆจกๅ—ๅทฒไธŠไผ ๏ผˆโœ…๏ผ‰๏ผŒ็ปง็ปญๆญฃๅธธ้—ญ็Žฏๆต็จ‹'); + parts.push('- ๅฆ‚ๆžœๆจกๅ—ๆœชไธŠไผ ๏ผˆโŒ๏ผ‰๏ผŒๅœจ้ชŒๆ”ถๆŠฅๅ‘Šไธญๆ ‡ๆณจ"้œ€่กฅๅ……"๏ผŒๅนถๅœจๅ้ฆˆไธญๆŒ‡ๅ‡บ็ผบๅคฑ็š„ๆจกๅ—'); + } + + // โ”โ”โ” Notion ๅ›žไผ ็ป“ๆžœๆณจๅ…ฅ๏ผˆไบŒๆฌกๆ ธ้ชŒ๏ผ‰ โ”โ”โ” + if (NOTION_CALLBACK_RESULT) { + parts.push(''); + parts.push('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + parts.push('## ๐Ÿ“ฅ Notion ไพงๅค„็†็ป“ๆžœ๏ผˆๅ›žไผ ๆ ธ้ชŒ๏ผ‰'); + parts.push('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + parts.push(''); + parts.push('ไปฅไธ‹ๆ˜ฏ Notion ไพงๆ ธๅฟƒๅคง่„‘ไบบๆ ผไฝ“ๅค„็†ๅŽ็š„ๅ›žไผ ็ป“ๆžœ๏ผš'); + parts.push(NOTION_CALLBACK_RESULT); + parts.push(''); + parts.push('่ฏทๆ ธๅฏนไปฅไธŠๅ›žไผ ๅ†…ๅฎน๏ผŒ็กฎ่ฎคๆ–ฐๅนฟๆ’ญ็”Ÿๆˆๆ— ่ฏฏๅŽ่พ“ๅ‡บๆœ€็ปˆ็กฎ่ฎคใ€‚'); + } + // โ”โ”โ” ไปปๅŠก็ฑปๅž‹ไธ“็”จๆŒ‡ไปค โ”โ”โ” if (type === 'syslog') { parts.push(''); @@ -773,17 +806,19 @@ async function buildSystemPrompt(type, broadcastId, author) { parts.push(''); parts.push('ไฝ ้œ€่ฆๅฎŒๆˆไปฅไธ‹ๅทฅไฝœ๏ผš'); parts.push('1. ้ชŒๆ”ถ SYSLOG๏ผˆๆฃ€ๆŸฅ MODULE_LOG ๅฎŒๆ•ดๆ€ง๏ผ‰'); - parts.push('2. ๆŸฅ่ฏข็”ปๅƒๅบ“ๆœ€่ฟ‘ 2-3 ๆกๅฟซ็…ง๏ผˆPGP v1.0๏ผ‰'); - parts.push('3. ๆŸฅ่ฏขๆจกๅ—ๆŒ‡็บนๆณจๅ†Œ่กจ๏ผˆ้˜ฒ้‡ๅคยทโ‘จ.5๏ผ‰'); - parts.push('4. RT-02 ่‡ชๅŠจ่ฐƒๅบฆๅˆคๆ–ญ'); - parts.push('5. ็”Ÿๆˆๆ–ฐๅนฟๆ’ญ๏ผˆBC-GEN v4.0 ๅฎŒๆ•ดๆต็จ‹๏ผ‰'); - parts.push('6. ่พ“ๅ‡บ็ป“ๆž„ๅŒ–็ป“ๆžœ๏ผˆๅนฟๆ’ญๅ…จๆ–‡ + ้—ญ็Žฏๆ•ฐๆฎ๏ผ‰'); + parts.push('2. ๆฃ€ๆŸฅๆจกๅ—ไธŠไผ ้ชŒ่ฏ็ป“ๆžœ๏ผˆ้“ธๆธŠ Agent ๅทฒ่‡ชๅŠจๆฃ€ๆต‹๏ผ‰'); + parts.push('3. ๆŸฅ่ฏข็”ปๅƒๅบ“ๆœ€่ฟ‘ 2-3 ๆกๅฟซ็…ง๏ผˆPGP v1.0๏ผ‰'); + parts.push('4. ๆŸฅ่ฏขๆจกๅ—ๆŒ‡็บนๆณจๅ†Œ่กจ๏ผˆ้˜ฒ้‡ๅคยทโ‘จ.5๏ผ‰'); + parts.push('5. RT-02 ่‡ชๅŠจ่ฐƒๅบฆๅˆคๆ–ญ'); + parts.push('6. ็”Ÿๆˆๆ–ฐๅนฟๆ’ญ๏ผˆBC-GEN v4.0 ๅฎŒๆ•ดๆต็จ‹๏ผ‰'); + parts.push('7. ่พ“ๅ‡บ็ป“ๆž„ๅŒ–็ป“ๆžœ๏ผˆๅนฟๆ’ญๅ…จๆ–‡ + ้—ญ็Žฏๆ•ฐๆฎ๏ผ‰'); parts.push(''); parts.push('่พ“ๅ‡บๆ ผๅผ๏ผš'); parts.push('---'); parts.push('## ๐Ÿ“ก SYSLOG ้ชŒๆ”ถๆŠฅๅ‘Š'); parts.push('### ๅนฟๆ’ญ็ผ–ๅท๏ผš[็ผ–ๅท]'); parts.push('### ้ชŒๆ”ถ็ป“ๆžœ๏ผš[้€š่ฟ‡/้œ€่กฅๅ……]'); + parts.push('### ๆจกๅ—้ชŒ่ฏ๏ผš[ๅทฒไธŠไผ /ๆœชไธŠไผ ๏ผŒๅˆ—ๅ‡บ่ฏฆๆƒ…]'); parts.push('### ๅทฅไฝœๆ€ป็ป“๏ผš[ๆ‘˜่ฆ]'); parts.push('### ็”ปๅƒ่ฏ„ไผฐ๏ผš[PGP ไบ”็ปดๅบฆ่ฏ„ๅˆ†]'); parts.push('### ่ฐƒๅบฆๅˆคๆ–ญ๏ผš[RT-02 ไธ‹ไธ€ๆญฅ]');