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 + '
',
+ '
',
+ '
',
+ '
'
+ ].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 ไธไธๆญฅ]');