guanghulab/scripts/invoke-persona.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

361 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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.jsfork 子进程)
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<<EOF_\d+\n([\s\S]*?)\nEOF_\d+/);
if (resultMatch) result = resultMatch[1];
}
if (!result) result = stdout.slice(-3000);
resolve(result);
} else {
reject(new Error('wake-persona.js exited with code ' + code + ': ' + stderr));
}
});
child.on('error', reject);
});
}
// ══════════════════════════════════════════════════════════
// Step 3: 回写 Notion 工单(更新 receipt_status
// ══════════════════════════════════════════════════════════
async function writeResultsToNotion(workOrderId, result, taskId) {
if (!workOrderId || !NOTION_TOKEN) {
console.log('⚠️ 无法回写 Notion缺少 workOrderId 或 NOTION_TOKEN');
return;
}
console.log('📝 回写 Notion 工单结果...');
try {
// 更新工单状态
await notionRequest('PATCH', '/v1/pages/' + workOrderId, {
properties: {
'状态': { select: { name: '✅ 已完成' } },
},
});
console.log(' → 工单状态更新为 ✅ 已完成');
// 在工单页面追加处理结果
var resultBlocks = [
{
object: 'block',
type: 'heading_2',
heading_2: {
rich_text: [{ type: 'text', text: { content: '🧠 人格体处理结果' } }],
},
},
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{ type: 'text', text: { content: '处理时间: ' + new Date().toISOString() } }],
},
},
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{ type: 'text', text: { content: 'receipt_status: completed' } }],
},
},
];
// 将人格体结果作为代码块
if (result) {
resultBlocks.push({
object: 'block',
type: 'code',
code: {
rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }],
language: 'markdown',
},
});
}
await notionRequest('PATCH', '/v1/blocks/' + workOrderId + '/children', {
children: resultBlocks,
});
console.log(' → 处理结果已追加到工单页面');
} catch (err) {
console.error('⚠️ Notion 回写失败: ' + err.message);
// 重试一次
try {
console.log(' → 重试回写...');
await notionRequest('PATCH', '/v1/pages/' + workOrderId, {
properties: {
'状态': { select: { name: '⚠️ 异常·等人工介入' } },
},
});
} catch (_) { /* ignore */ }
}
}
// ══════════════════════════════════════════════════════════
// Step 4: 输出结果
// ══════════════════════════════════════════════════════════
function outputResult(result) {
var outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
var delimiter = 'EOF_' + Date.now();
fs.appendFileSync(outputFile, 'persona_result<<' + delimiter + '\n' + result + '\n' + delimiter + '\n');
fs.appendFileSync(outputFile, 'invocation_status=completed\n');
}
}
// ══════════════════════════════════════════════════════════
// 主流程
// ══════════════════════════════════════════════════════════
async function main() {
console.log('═══════════════════════════════════════════');
console.log('🔗 铸渊 · 人格体唤醒调用Phase B2');
console.log('═══════════════════════════════════════════');
console.log(' workOrderId: ' + WORK_ORDER_ID);
console.log(' taskId: ' + TASK_ID);
console.log(' developer: ' + DEVELOPER);
console.log(' action: ' + ACTION);
console.log('');
// Step 1: 读取工单
var workOrder = await readWorkOrder();
if (!workOrder.syslog_raw) {
console.error('❌ 缺少 SYSLOG 原文,无法处理');
process.exit(1);
}
// Step 2: 唤醒人格体
var result;
try {
result = await invokePersona(workOrder);
} catch (err) {
console.error('❌ 人格体唤醒失败: ' + err.message);
result = '(人格体唤醒失败: ' + err.message + ')';
}
// Step 3: 回写 Notion
if (WORK_ORDER_ID) {
await writeResultsToNotion(WORK_ORDER_ID, result, workOrder.taskId);
}
// Step 4: 输出结果
outputResult(result || '(no result)');
console.log('');
console.log('✅ 人格体调用完成');
}
main().catch(function (err) {
console.error('❌ invoke-persona 失败: ' + err.message);
// 尝试标记工单异常
if (WORK_ORDER_ID && NOTION_TOKEN) {
notionRequest('PATCH', '/v1/pages/' + WORK_ORDER_ID, {
properties: {
'状态': { select: { name: '⚠️ 异常·等人工介入' } },
},
}).catch(function () { /* ignore */ });
}
process.exit(1);
});