📡 指令回执自动闭环系统 · write-receipt-to-notion.js + 11 Workflow 追加回执写入 step
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/f717b1c5-28b3-405e-9819-b987389fff6c
This commit is contained in:
parent
05519d82df
commit
cc55130cb7
|
|
@ -10,6 +10,12 @@
|
||||||
"last_synced": "2026-03-19T02:00:00+08:00"
|
"last_synced": "2026-03-19T02:00:00+08:00"
|
||||||
},
|
},
|
||||||
"recent_events": [
|
"recent_events": [
|
||||||
|
{
|
||||||
|
"date": "2026-03-25",
|
||||||
|
"type": "receipt_system_deploy",
|
||||||
|
"description": "指令回执自动闭环系统上线 · 11 Workflow 追加回执写入 · 本地兜底+Notion 双写",
|
||||||
|
"by": "铸渊"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-24",
|
"date": "2026-03-24",
|
||||||
"type": "skyeye_scan",
|
"type": "skyeye_scan",
|
||||||
|
|
@ -123,12 +129,6 @@
|
||||||
"type": "broadcast_received",
|
"type": "broadcast_received",
|
||||||
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
|
||||||
"by": "霜砚"
|
"by": "霜砚"
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "2026-03-10",
|
|
||||||
"type": "ci_run",
|
|
||||||
"description": "CI完成 · schema 3/17 · 路由 4 个",
|
|
||||||
"by": "GitHub Actions"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_updated": "2026-03-25T01:48:03Z",
|
"last_updated": "2026-03-25T01:48:03Z",
|
||||||
|
|
@ -197,5 +197,14 @@
|
||||||
"signed_by": "TCS-0002∞",
|
"signed_by": "TCS-0002∞",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"notion_source": "数字地球本体论 · 光湖语言膜架构 v1.0"
|
"notion_source": "数字地球本体论 · 光湖语言膜架构 v1.0"
|
||||||
|
},
|
||||||
|
"receipt_system": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"deployed": "2026-03-25",
|
||||||
|
"script": "scripts/neural/write-receipt-to-notion.js",
|
||||||
|
"tracker": "scripts/neural/track-work-orders.js",
|
||||||
|
"receipt_dir": "data/neural-reports/receipts",
|
||||||
|
"workflows_count": 11,
|
||||||
|
"notion_db_env_var": "RECEIPT_DB_ID"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -767,3 +767,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "cd-deploy" \
|
||||||
|
--summary "cd-deploy 自动执行" \
|
||||||
|
--related-agent "AG-SY-01"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,3 +70,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "broadcast" \
|
||||||
|
--summary "broadcast 自动执行" \
|
||||||
|
--related-agent "SY-03"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -206,3 +206,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "neural-daily-digest" \
|
||||||
|
--summary "neural-daily-digest 自动执行" \
|
||||||
|
--related-agent "AG-TY-01"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,3 +203,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "server-patrol" \
|
||||||
|
--summary "server-patrol 自动执行" \
|
||||||
|
--related-agent "AG-TY-01"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,3 +97,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "weekly-scan" \
|
||||||
|
--summary "weekly-scan 自动执行" \
|
||||||
|
--related-agent "AG-TY-01"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,3 +147,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "dev-status" \
|
||||||
|
--summary "dev-status 自动执行" \
|
||||||
|
--related-agent "SY-03"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,3 +347,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "syslog" \
|
||||||
|
--summary "syslog 自动执行" \
|
||||||
|
--related-agent "SY-03"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,3 +161,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "brain-check" \
|
||||||
|
--summary "brain-check 自动执行" \
|
||||||
|
--related-agent "AG-TY-01"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,3 +156,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "exec-engine" \
|
||||||
|
--summary "exec-engine 自动执行" \
|
||||||
|
--related-agent "AG-SY-01"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,3 +207,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "gate-guard" \
|
||||||
|
--summary "gate-guard 自动执行" \
|
||||||
|
--related-agent "AG-SY-01"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -245,3 +245,17 @@ jobs:
|
||||||
git diff --cached --quiet || \
|
git diff --cached --quiet || \
|
||||||
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
|
||||||
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
git push || echo "⚠️ 自报告push失败(不阻断主流程)"
|
||||||
|
# ━━━ 📡 指令回执·自动同步 ━━━
|
||||||
|
- name: "📡 同步回执到 Notion"
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
|
||||||
|
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
|
||||||
|
run: |
|
||||||
|
node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
--instruction-id "${{ github.event.inputs.instruction_id || 'AUTO' }}" \
|
||||||
|
--status "${{ job.status }}" \
|
||||||
|
--workflow "skyeye" \
|
||||||
|
--summary "skyeye 自动执行" \
|
||||||
|
--related-agent "AG-TY-01"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"tracker_id": "TRACKER-2026-03-25",
|
||||||
|
"timestamp": "2026-03-25T06:50:36.021Z",
|
||||||
|
"receipt_config": {
|
||||||
|
"database_env_var": "RECEIPT_DB_ID",
|
||||||
|
"timeout_warning_hours": 24,
|
||||||
|
"timeout_critical_hours": 72
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"total_active": 0,
|
||||||
|
"by_status": {},
|
||||||
|
"timed_out": 0,
|
||||||
|
"matched_receipts": 0,
|
||||||
|
"escalated": 0
|
||||||
|
},
|
||||||
|
"active_orders": []
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,47 @@
|
||||||
// 🧬 工单追踪器
|
// 🧬 工单追踪器
|
||||||
// 铸渊每次唤醒时运行,追踪所有活跃工单状态
|
// 铸渊每次唤醒时运行,追踪所有活跃工单状态
|
||||||
// 检查超时、匹配回执、更新状态、生成追踪报告
|
// 检查超时、匹配回执、更新状态、生成追踪报告
|
||||||
|
//
|
||||||
|
// 指令回执追踪表配置(Notion 侧)
|
||||||
|
// 数据库位置:光湖语言壳 > 统一网关 > 指令回执追踪表
|
||||||
|
// 写入脚本:scripts/neural/write-receipt-to-notion.js
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
var WORK_ORDER_DIR = 'data/neural-reports/work-orders';
|
var WORK_ORDER_DIR = 'data/neural-reports/work-orders';
|
||||||
var COMPLETED_DIR = 'data/deploy-queue/completed';
|
var COMPLETED_DIR = 'data/deploy-queue/completed';
|
||||||
|
var RECEIPT_DIR = 'data/neural-reports/receipts';
|
||||||
var TRACKER_DIR = 'data/neural-reports/work-orders';
|
var TRACKER_DIR = 'data/neural-reports/work-orders';
|
||||||
var TERMINAL_STATUSES = ['CLOSED', 'ESCALATED', 'VERIFIED'];
|
var TERMINAL_STATUSES = ['CLOSED', 'ESCALATED', 'VERIFIED'];
|
||||||
|
|
||||||
|
// ━━━ 指令回执追踪配置 ━━━
|
||||||
|
var RECEIPT_TRACKER_CONFIG = {
|
||||||
|
// 指令回执追踪表 · Notion Database ID
|
||||||
|
// 位于:光湖语言壳 > 统一网关 > 指令回执追踪表
|
||||||
|
// 实际 ID 通过环境变量 RECEIPT_DB_ID 传入(安全考虑不硬编码)
|
||||||
|
databaseIdEnvVar: 'RECEIPT_DB_ID',
|
||||||
|
|
||||||
|
// 字段映射(与 Notion 数据库字段名一一对应)
|
||||||
|
fieldMap: {
|
||||||
|
instructionId: '指令编号', // title
|
||||||
|
summary: '指令摘要', // text
|
||||||
|
status: '执行状态', // status
|
||||||
|
receipt: '铸渊回执', // text
|
||||||
|
receiptTime: '回执时间', // date
|
||||||
|
newFiles: '新增文件数', // number
|
||||||
|
modifiedFiles: '修改文件数', // number
|
||||||
|
relatedAgent: '关联Agent', // text
|
||||||
|
timeoutStatus: '回执超时', // select
|
||||||
|
},
|
||||||
|
|
||||||
|
// 超时阈值(小时)
|
||||||
|
timeoutThresholds: {
|
||||||
|
warning: 24, // 24h → 🟡 接近超时
|
||||||
|
critical: 72, // 72h → 🔴 已超时
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function loadJSON(p) {
|
function loadJSON(p) {
|
||||||
try { return JSON.parse(fs.readFileSync(p, 'utf8')); }
|
try { return JSON.parse(fs.readFileSync(p, 'utf8')); }
|
||||||
catch (e) { return null; }
|
catch (e) { return null; }
|
||||||
|
|
@ -38,29 +70,55 @@ function getActiveWorkOrders() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCompletedReceipts() {
|
function getCompletedReceipts() {
|
||||||
if (!fs.existsSync(COMPLETED_DIR)) return {};
|
|
||||||
|
|
||||||
var receipts = {};
|
var receipts = {};
|
||||||
var files = fs.readdirSync(COMPLETED_DIR)
|
|
||||||
.filter(function(f) { return f.endsWith('.json'); });
|
|
||||||
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
// 从 deploy-queue/completed 收集
|
||||||
var receipt = loadJSON(path.join(COMPLETED_DIR, files[i]));
|
if (fs.existsSync(COMPLETED_DIR)) {
|
||||||
if (receipt && receipt.command_id) {
|
var files = fs.readdirSync(COMPLETED_DIR)
|
||||||
receipts[receipt.command_id] = receipt;
|
.filter(function(f) { return f.endsWith('.json'); });
|
||||||
|
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
var receipt = loadJSON(path.join(COMPLETED_DIR, files[i]));
|
||||||
|
if (receipt && receipt.command_id) {
|
||||||
|
receipts[receipt.command_id] = receipt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从 neural-reports/receipts 收集本地回执
|
||||||
|
if (fs.existsSync(RECEIPT_DIR)) {
|
||||||
|
var receiptFiles = fs.readdirSync(RECEIPT_DIR)
|
||||||
|
.filter(function(f) { return f.endsWith('.json'); });
|
||||||
|
|
||||||
|
for (var j = 0; j < receiptFiles.length; j++) {
|
||||||
|
var localReceipt = loadJSON(path.join(RECEIPT_DIR, receiptFiles[j]));
|
||||||
|
if (localReceipt && localReceipt.instruction_id) {
|
||||||
|
receipts[localReceipt.instruction_id] = localReceipt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return receipts;
|
return receipts;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkTimeout(workOrder) {
|
function checkTimeout(workOrder) {
|
||||||
var timeout = workOrder.timeout_hours || 24;
|
var timeout = workOrder.timeout_hours || RECEIPT_TRACKER_CONFIG.timeoutThresholds.critical;
|
||||||
var created = new Date(workOrder.created);
|
var created = new Date(workOrder.created);
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
var elapsed = (now - created) / (1000 * 60 * 60);
|
var elapsed = (now - created) / (1000 * 60 * 60);
|
||||||
return elapsed > timeout;
|
return elapsed > timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTimeoutStatus(workOrder) {
|
||||||
|
var created = new Date(workOrder.created);
|
||||||
|
var now = new Date();
|
||||||
|
var elapsed = (now - created) / (1000 * 60 * 60);
|
||||||
|
|
||||||
|
if (elapsed > RECEIPT_TRACKER_CONFIG.timeoutThresholds.critical) return '🔴 已超时';
|
||||||
|
if (elapsed > RECEIPT_TRACKER_CONFIG.timeoutThresholds.warning) return '🟡 接近超时';
|
||||||
|
return '🟢 正常';
|
||||||
|
}
|
||||||
|
|
||||||
function trackWorkOrders() {
|
function trackWorkOrders() {
|
||||||
console.log('\n━━━ 🧬 工单追踪器启动 ━━━\n');
|
console.log('\n━━━ 🧬 工单追踪器启动 ━━━\n');
|
||||||
|
|
||||||
|
|
@ -123,6 +181,11 @@ function trackWorkOrders() {
|
||||||
var trackerReport = {
|
var trackerReport = {
|
||||||
tracker_id: 'TRACKER-' + dateStr,
|
tracker_id: 'TRACKER-' + dateStr,
|
||||||
timestamp: now.toISOString(),
|
timestamp: now.toISOString(),
|
||||||
|
receipt_config: {
|
||||||
|
database_env_var: RECEIPT_TRACKER_CONFIG.databaseIdEnvVar,
|
||||||
|
timeout_warning_hours: RECEIPT_TRACKER_CONFIG.timeoutThresholds.warning,
|
||||||
|
timeout_critical_hours: RECEIPT_TRACKER_CONFIG.timeoutThresholds.critical
|
||||||
|
},
|
||||||
stats: stats,
|
stats: stats,
|
||||||
active_orders: activeOrders.map(function(wo) {
|
active_orders: activeOrders.map(function(wo) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -131,7 +194,8 @@ function trackWorkOrders() {
|
||||||
title: wo.title,
|
title: wo.title,
|
||||||
status: wo.status,
|
status: wo.status,
|
||||||
created: wo.created,
|
created: wo.created,
|
||||||
timed_out: checkTimeout(wo)
|
timed_out: checkTimeout(wo),
|
||||||
|
timeout_status: getTimeoutStatus(wo)
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
@ -157,4 +221,4 @@ if (require.main === module) {
|
||||||
trackWorkOrders();
|
trackWorkOrders();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { trackWorkOrders };
|
module.exports = { trackWorkOrders, RECEIPT_TRACKER_CONFIG };
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,359 @@
|
||||||
|
// scripts/neural/write-receipt-to-notion.js
|
||||||
|
// 📡 指令回执自动写入 Notion
|
||||||
|
// 铸渊每次执行完指令后,自动将回执写入 Notion「指令回执追踪表」
|
||||||
|
// 实现 指令→执行→回执 全链路自动闭环
|
||||||
|
//
|
||||||
|
// 用法:
|
||||||
|
// node scripts/neural/write-receipt-to-notion.js \
|
||||||
|
// --instruction-id "ZY-XXX" \
|
||||||
|
// --status "success" \
|
||||||
|
// --workflow "workflow-name" \
|
||||||
|
// [--summary "执行摘要"] \
|
||||||
|
// [--receipt "回执详情"] \
|
||||||
|
// [--new-files 0] \
|
||||||
|
// [--modified-files 0] \
|
||||||
|
// [--related-agent "AG-TY-01"]
|
||||||
|
//
|
||||||
|
// 环境变量:
|
||||||
|
// NOTION_API_KEY — Notion API 密钥(必须)
|
||||||
|
// RECEIPT_DB_ID — 指令回执追踪表的 Notion Database ID(必须)
|
||||||
|
//
|
||||||
|
// 铁律:
|
||||||
|
// 1. 写入失败不阻塞主流程
|
||||||
|
// 2. 同一指令编号幂等(重复写入 = 更新)
|
||||||
|
// 3. 失败时写入本地 SYSLOG 报错
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var https = require('https');
|
||||||
|
|
||||||
|
// ━━━ 配置 ━━━
|
||||||
|
|
||||||
|
var RECEIPT_TRACKER_CONFIG = {
|
||||||
|
// 字段映射 — Notion 数据库字段名
|
||||||
|
fieldMap: {
|
||||||
|
instructionId: '指令编号',
|
||||||
|
summary: '指令摘要',
|
||||||
|
status: '执行状态',
|
||||||
|
receipt: '铸渊回执',
|
||||||
|
receiptTime: '回执时间',
|
||||||
|
newFiles: '新增文件数',
|
||||||
|
modifiedFiles: '修改文件数',
|
||||||
|
relatedAgent: '关联Agent',
|
||||||
|
timeoutStatus: '回执超时'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 超时阈值(小时)
|
||||||
|
timeoutThresholds: {
|
||||||
|
warning: 24,
|
||||||
|
critical: 72
|
||||||
|
},
|
||||||
|
|
||||||
|
// 状态映射
|
||||||
|
statusMap: {
|
||||||
|
'success': '✅ 已完成',
|
||||||
|
'failure': '❌ 执行失败',
|
||||||
|
'cancelled': '⏸️ 已挂起',
|
||||||
|
'skipped': '⏸️ 已挂起'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var LOCAL_RECEIPT_DIR = 'data/neural-reports/receipts';
|
||||||
|
var SYSLOG_DIR = 'data/neural-reports/syslog';
|
||||||
|
|
||||||
|
// ━━━ 参数解析 ━━━
|
||||||
|
|
||||||
|
function parseArgs() {
|
||||||
|
var args = process.argv.slice(2);
|
||||||
|
var parsed = {};
|
||||||
|
|
||||||
|
for (var i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] === '--instruction-id' && args[i + 1]) {
|
||||||
|
parsed.instructionId = args[++i];
|
||||||
|
} else if (args[i] === '--status' && args[i + 1]) {
|
||||||
|
parsed.status = args[++i];
|
||||||
|
} else if (args[i] === '--workflow' && args[i + 1]) {
|
||||||
|
parsed.workflow = args[++i];
|
||||||
|
} else if (args[i] === '--summary' && args[i + 1]) {
|
||||||
|
parsed.summary = args[++i];
|
||||||
|
} else if (args[i] === '--receipt' && args[i + 1]) {
|
||||||
|
parsed.receipt = args[++i];
|
||||||
|
} else if (args[i] === '--new-files' && args[i + 1]) {
|
||||||
|
parsed.newFiles = parseInt(args[++i], 10) || 0;
|
||||||
|
} else if (args[i] === '--modified-files' && args[i + 1]) {
|
||||||
|
parsed.modifiedFiles = parseInt(args[++i], 10) || 0;
|
||||||
|
} else if (args[i] === '--related-agent' && args[i + 1]) {
|
||||||
|
parsed.relatedAgent = args[++i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ━━━ Notion API 封装(纯 https,无外部依赖)━━━
|
||||||
|
|
||||||
|
function notionRequest(method, endpoint, body, token) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var bodyStr = body ? JSON.stringify(body) : '';
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
hostname: 'api.notion.com',
|
||||||
|
port: 443,
|
||||||
|
path: '/v1' + endpoint,
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + token,
|
||||||
|
'Notion-Version': '2022-06-28',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': Buffer.byteLength(bodyStr)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var req = https.request(options, 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 response parse error: ' + data.substring(0, 200)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', function(e) {
|
||||||
|
reject(new Error('Notion API request error: ' + e.message));
|
||||||
|
});
|
||||||
|
|
||||||
|
req.setTimeout(30000, function() {
|
||||||
|
req.destroy();
|
||||||
|
reject(new Error('Notion API request timeout (30s)'));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bodyStr) req.write(bodyStr);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ━━━ Notion 属性构建 ━━━
|
||||||
|
|
||||||
|
function buildNotionProperties(receipt) {
|
||||||
|
var fm = RECEIPT_TRACKER_CONFIG.fieldMap;
|
||||||
|
var statusText = RECEIPT_TRACKER_CONFIG.statusMap[receipt.status] || '⏸️ 已挂起';
|
||||||
|
|
||||||
|
var properties = {};
|
||||||
|
|
||||||
|
// 指令编号 (title)
|
||||||
|
properties[fm.instructionId] = {
|
||||||
|
title: [{ text: { content: receipt.instructionId || 'AUTO' } }]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 指令摘要 (rich_text)
|
||||||
|
properties[fm.summary] = {
|
||||||
|
rich_text: [{ text: { content: receipt.summary || receipt.workflow || '' } }]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行状态 (status)
|
||||||
|
properties[fm.status] = {
|
||||||
|
status: { name: statusText }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 铸渊回执 (rich_text)
|
||||||
|
var receiptText = receipt.receipt || (statusText + ' · ' + (receipt.workflow || ''));
|
||||||
|
properties[fm.receipt] = {
|
||||||
|
rich_text: [{ text: { content: receiptText.substring(0, 2000) } }]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 回执时间 (date)
|
||||||
|
properties[fm.receiptTime] = {
|
||||||
|
date: { start: new Date().toISOString().split('T')[0] }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增文件数 (number)
|
||||||
|
if (typeof receipt.newFiles === 'number') {
|
||||||
|
properties[fm.newFiles] = { number: receipt.newFiles };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改文件数 (number)
|
||||||
|
if (typeof receipt.modifiedFiles === 'number') {
|
||||||
|
properties[fm.modifiedFiles] = { number: receipt.modifiedFiles };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关联Agent (rich_text)
|
||||||
|
if (receipt.relatedAgent) {
|
||||||
|
properties[fm.relatedAgent] = {
|
||||||
|
rich_text: [{ text: { content: receipt.relatedAgent } }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回执超时 (select)
|
||||||
|
properties[fm.timeoutStatus] = {
|
||||||
|
select: { name: '🟢 正常' }
|
||||||
|
};
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ━━━ 写入 Notion ━━━
|
||||||
|
|
||||||
|
async function writeReceiptToNotion(receipt, token, databaseId) {
|
||||||
|
console.log('📡 写入回执到 Notion · 指令: ' + receipt.instructionId);
|
||||||
|
|
||||||
|
// 先查询是否已有该指令编号的记录(幂等)
|
||||||
|
var queryBody = {
|
||||||
|
database_id: databaseId,
|
||||||
|
filter: {
|
||||||
|
property: RECEIPT_TRACKER_CONFIG.fieldMap.instructionId,
|
||||||
|
title: { equals: receipt.instructionId }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var existing = await notionRequest('POST', '/databases/' + databaseId + '/query', queryBody, token);
|
||||||
|
|
||||||
|
var properties = buildNotionProperties(receipt);
|
||||||
|
|
||||||
|
if (existing.results && existing.results.length > 0) {
|
||||||
|
// 更新已有记录
|
||||||
|
var pageId = existing.results[0].id;
|
||||||
|
await notionRequest('PATCH', '/pages/' + pageId, { properties: properties }, token);
|
||||||
|
console.log('✅ 已更新现有记录: ' + pageId);
|
||||||
|
} else {
|
||||||
|
// 创建新记录
|
||||||
|
await notionRequest('POST', '/pages', {
|
||||||
|
parent: { database_id: databaseId },
|
||||||
|
properties: properties
|
||||||
|
}, token);
|
||||||
|
console.log('✅ 已创建新记录');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ━━━ 本地兜底写入 ━━━
|
||||||
|
|
||||||
|
function writeLocalReceipt(receipt) {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(LOCAL_RECEIPT_DIR, { recursive: true });
|
||||||
|
|
||||||
|
var now = new Date();
|
||||||
|
var cstTime = new Date(now.getTime() + 8 * 60 * 60 * 1000);
|
||||||
|
var dateStr = cstTime.toISOString().split('T')[0];
|
||||||
|
var timeStr = cstTime.toISOString().replace(/[-:]/g, '').split('.')[0];
|
||||||
|
|
||||||
|
var filename = 'receipt-' + timeStr + '.json';
|
||||||
|
var fullPath = path.join(LOCAL_RECEIPT_DIR, filename);
|
||||||
|
|
||||||
|
var localReceipt = {
|
||||||
|
instruction_id: receipt.instructionId,
|
||||||
|
workflow: receipt.workflow,
|
||||||
|
status: receipt.status,
|
||||||
|
summary: receipt.summary || '',
|
||||||
|
receipt_text: receipt.receipt || '',
|
||||||
|
new_files: receipt.newFiles || 0,
|
||||||
|
modified_files: receipt.modifiedFiles || 0,
|
||||||
|
related_agent: receipt.relatedAgent || '',
|
||||||
|
timestamp: now.toISOString(),
|
||||||
|
notion_synced: false
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(fullPath, JSON.stringify(localReceipt, null, 2));
|
||||||
|
console.log('💾 本地回执已保存: ' + fullPath);
|
||||||
|
return fullPath;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ 本地回执保存失败: ' + e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ━━━ SYSLOG 报错 ━━━
|
||||||
|
|
||||||
|
function writeSyslogError(message, receipt) {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(SYSLOG_DIR, { recursive: true });
|
||||||
|
|
||||||
|
var now = new Date();
|
||||||
|
var cstTime = new Date(now.getTime() + 8 * 60 * 60 * 1000);
|
||||||
|
var timeStr = cstTime.toISOString().replace(/[-:]/g, '').split('.')[0];
|
||||||
|
|
||||||
|
var logEntry = {
|
||||||
|
level: 'ERROR',
|
||||||
|
source: 'write-receipt-to-notion',
|
||||||
|
message: message,
|
||||||
|
instruction_id: receipt ? receipt.instructionId : 'unknown',
|
||||||
|
timestamp: now.toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
var filename = 'receipt-error-' + timeStr + '.json';
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(SYSLOG_DIR, filename),
|
||||||
|
JSON.stringify(logEntry, null, 2)
|
||||||
|
);
|
||||||
|
console.error('📝 错误已记录到 SYSLOG: ' + filename);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('⚠️ SYSLOG 写入也失败: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ━━━ 主函数 ━━━
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('\n━━━ 📡 指令回执写入 ━━━\n');
|
||||||
|
|
||||||
|
var receipt = parseArgs();
|
||||||
|
|
||||||
|
if (!receipt.instructionId && !receipt.workflow) {
|
||||||
|
console.log('⚠️ 未提供 instruction-id 或 workflow,跳过回执写入');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有指令ID,用 workflow + 日期 自动生成
|
||||||
|
if (!receipt.instructionId || receipt.instructionId === 'AUTO') {
|
||||||
|
var now = new Date();
|
||||||
|
var cstTime = new Date(now.getTime() + 8 * 60 * 60 * 1000);
|
||||||
|
var dateStr = cstTime.toISOString().split('T')[0].replace(/-/g, '');
|
||||||
|
receipt.instructionId = 'AUTO-' + (receipt.workflow || 'unknown') + '-' + dateStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 始终写入本地兜底
|
||||||
|
writeLocalReceipt(receipt);
|
||||||
|
|
||||||
|
// 尝试写入 Notion
|
||||||
|
var token = process.env.NOTION_API_KEY;
|
||||||
|
var databaseId = process.env.RECEIPT_DB_ID;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
var msg = '⚠️ NOTION_API_KEY 未设置,仅保存本地回执';
|
||||||
|
console.log(msg);
|
||||||
|
writeSyslogError(msg, receipt);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!databaseId) {
|
||||||
|
var msg = '⚠️ RECEIPT_DB_ID 未设置,仅保存本地回执';
|
||||||
|
console.log(msg);
|
||||||
|
writeSyslogError(msg, receipt);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await writeReceiptToNotion(receipt, token, databaseId);
|
||||||
|
console.log('\n✅ 回执写入完成\n');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ Notion 写入失败: ' + e.message);
|
||||||
|
writeSyslogError('Notion 写入失败: ' + e.message, receipt);
|
||||||
|
// 不阻塞主流程,退出码 0
|
||||||
|
console.log('💾 已有本地兜底,不影响主流程');
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(function(err) {
|
||||||
|
console.error('❌ 回执脚本异常: ' + err.message);
|
||||||
|
process.exit(0); // 不阻塞
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue