diff --git a/.github/workflows/notion-callback-pipeline.yml b/.github/workflows/notion-callback-pipeline.yml index a6fc2d39..463d7ced 100644 --- a/.github/workflows/notion-callback-pipeline.yml +++ b/.github/workflows/notion-callback-pipeline.yml @@ -109,6 +109,69 @@ jobs: 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 }} diff --git a/.github/workflows/notion-heartbeat.yml b/.github/workflows/notion-heartbeat.yml new file mode 100644 index 00000000..d7b5969a --- /dev/null +++ b/.github/workflows/notion-heartbeat.yml @@ -0,0 +1,44 @@ +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 + + 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 02eb3e5e..a0d84b10 100644 --- a/.github/workflows/syslog-auto-pipeline.yml +++ b/.github/workflows/syslog-auto-pipeline.yml @@ -130,7 +130,8 @@ jobs: 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 }} @@ -140,93 +141,18 @@ jobs: PERSONA_RESULT: ${{ steps.persona.outputs.result }} MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }} MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }} - AUTHOR: ${{ steps.parse.outputs.author }} - run: | - node -e " - const https = require('https'); + 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)'; - const modulesUploaded = process.env.MODULES_UPLOADED || 'false'; - const moduleVerify = process.env.MODULE_VERIFY || '{}'; - const author = process.env.AUTHOR || 'unknown'; - - if (!token || !dbId) { - console.log('โš ๏ธ Notion credentials not configured, skipping ticket creation'); - process.exit(0); - } - - const typeLabel = type === 'syslog' ? 'SYSLOG้—ญ็Žฏ' : 'ๆ้—ฎ่งฃ็ญ”'; - const verifyStatus = modulesUploaded === 'true' ? 'ๆจกๅ—ๅทฒ้ชŒ่ฏโœ…' : 'ๆจกๅ—ๅพ…้ชŒ่ฏโš ๏ธ'; - const title = '[่‡ชๅŠจ] ' + broadcastId + ' ยท ' + typeLabel + ' ยท ' + verifyStatus; - - // ๆž„ๅปบๅทฅๅ•ๅ†…ๅฎน๏ผšๅŒ…ๅซไบบๆ ผไฝ“ๅค„็†็ป“ๆžœ + ๆจกๅ—้ชŒ่ฏๆ•ฐๆฎ - const ticketContent = [ - '## ้“ธๆธŠๆ ธๅฟƒๅคง่„‘ๅค„็†็ป“ๆžœ', - '', - result.slice(0, 1500), - '', - '## ๆจกๅ—ไธŠไผ ้ชŒ่ฏ', - '', - 'ๆจกๅ—ๅ…จ้ƒจไธŠไผ : ' + (modulesUploaded === 'true' ? 'โœ… ๆ˜ฏ' : 'โŒ ๅฆ'), - 'ๆไบค่€…: ' + author, - '', - '้ชŒ่ฏ่ฏฆๆƒ…: ' + moduleVerify.slice(0, 400), - ].join('\\n'); - - // SYSLOG็ฑปๅž‹ๅทฅๅ•่ฎพไธบ'ๅพ…ๅค„็†'๏ผˆ็ญ‰ๅพ…Notionไพงๆ ธๅฟƒๅคง่„‘ๅค„็†๏ผ‰ - // ๆ้—ฎ็ฑปๅž‹ๅทฅๅ•่ฎพไธบ'ๅทฒๅฎŒๆˆ'๏ผˆ้“ธๆธŠๅทฒ็”Ÿๆˆ่งฃ็ญ”๏ผ‰ - const ticketStatus = type === 'syslog' ? 'ๅพ…ๅค„็†' : 'ๅทฒๅฎŒๆˆ'; - - 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: '้“ธๆธŠAgentยท่‡ชๅŠจ็ฎก้“' } }] }, - '็Šถๆ€': { select: { name: ticketStatus } }, - 'ไผ˜ๅ…ˆ็บง': { select: { name: 'P1' } } - }, - children: [{ - object: 'block', - type: 'paragraph', - paragraph: { - rich_text: [{ type: 'text', text: { content: ticketContent.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: diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml index 4acbc3b0..27ac9cfc 100644 --- a/.github/workflows/syslog-issue-pipeline.yml +++ b/.github/workflows/syslog-issue-pipeline.yml @@ -130,7 +130,8 @@ jobs: 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 }} @@ -140,93 +141,18 @@ jobs: PERSONA_RESULT: ${{ steps.persona.outputs.result }} MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }} MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }} - AUTHOR: ${{ steps.parse.outputs.author }} - run: | - node -e " - const https = require('https'); + 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)'; - const modulesUploaded = process.env.MODULES_UPLOADED || 'false'; - const moduleVerify = process.env.MODULE_VERIFY || '{}'; - const author = process.env.AUTHOR || 'unknown'; - - if (!token || !dbId) { - console.log('โš ๏ธ Notion credentials not configured, skipping ticket creation'); - process.exit(0); - } - - const typeLabel = type === 'syslog' ? 'SYSLOG้—ญ็Žฏ' : 'ๆ้—ฎ่งฃ็ญ”'; - const verifyStatus = modulesUploaded === 'true' ? 'ๆจกๅ—ๅทฒ้ชŒ่ฏโœ…' : 'ๆจกๅ—ๅพ…้ชŒ่ฏโš ๏ธ'; - const title = '[่‡ชๅŠจ] ' + broadcastId + ' ยท ' + typeLabel + ' ยท ' + verifyStatus; - - // ๆž„ๅปบๅทฅๅ•ๅ†…ๅฎน๏ผšๅŒ…ๅซไบบๆ ผไฝ“ๅค„็†็ป“ๆžœ + ๆจกๅ—้ชŒ่ฏๆ•ฐๆฎ - const ticketContent = [ - '## ้“ธๆธŠๆ ธๅฟƒๅคง่„‘ๅค„็†็ป“ๆžœ', - '', - result.slice(0, 1500), - '', - '## ๆจกๅ—ไธŠไผ ้ชŒ่ฏ', - '', - 'ๆจกๅ—ๅ…จ้ƒจไธŠไผ : ' + (modulesUploaded === 'true' ? 'โœ… ๆ˜ฏ' : 'โŒ ๅฆ'), - 'ๆไบค่€…: ' + author, - '', - '้ชŒ่ฏ่ฏฆๆƒ…: ' + moduleVerify.slice(0, 400), - ].join('\\n'); - - // SYSLOG็ฑปๅž‹ๅทฅๅ•่ฎพไธบ'ๅพ…ๅค„็†'๏ผˆ็ญ‰ๅพ…Notionไพงๆ ธๅฟƒๅคง่„‘ๅค„็†๏ผ‰ - // ๆ้—ฎ็ฑปๅž‹ๅทฅๅ•่ฎพไธบ'ๅทฒๅฎŒๆˆ'๏ผˆ้“ธๆธŠๅทฒ็”Ÿๆˆ่งฃ็ญ”๏ผ‰ - const ticketStatus = type === 'syslog' ? 'ๅพ…ๅค„็†' : 'ๅทฒๅฎŒๆˆ'; - - 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: '้“ธๆธŠAgentยท่‡ชๅŠจ็ฎก้“' } }] }, - '็Šถๆ€': { select: { name: ticketStatus } }, - 'ไผ˜ๅ…ˆ็บง': { select: { name: 'P1' } } - }, - children: [{ - object: 'block', - type: 'paragraph', - paragraph: { - rich_text: [{ type: 'text', text: { content: ticketContent.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: 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..a1607245 --- /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 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) { + var fs = require('fs'); + 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..bd4cc12e --- /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'); + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ้…็ฝฎ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +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 childProcess = require('child_process'); + 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..32af6eb1 --- /dev/null +++ b/scripts/push-broadcast-to-github.js @@ -0,0 +1,179 @@ +// 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 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) { + // ไปŽ BROADCAST_ID ๆๅ–ๅŽ็ผ€ โ†’ ๆ˜ ๅฐ„ๅˆฐ DEV-XXX + var suffixMatch = BROADCAST_ID.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i); + var suffix = suffixMatch ? suffixMatch[1].toUpperCase() : ''; + var suffixMap = { + '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', + 'XX': 'DEV-013', 'SY': 'DEV-014', + }; + devId = suffixMap[suffix] || '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; }