guanghulab/scripts/grid-db/process-inbox.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

339 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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/grid-db/process-inbox.js
*
* Grid-DB Inbox 消息处理器
*
* 流程:
* ① 扫描 grid-db/inbox/ 中所有 .json 文件
* ② 校验 dev_id 合法性(必须在 DEV 映射表中)
* ③ 移动到 grid-db/processing/(锁定)
* ④ 根据消息 type 分流处理:
* - progress-update → 更新 task-queue + 触发广播生成
* - dev-log → 归档到 interactions + 更新 dev-profile
* - help-request → 标记 P0 + 读取 Notion 知识库 → 生成回复
* - syslog → 走标准 SYSLOG 处理流程
* - interaction-dump → 写入 interactions/ + 追加 training-lake/raw/
* ⑤ 生成 outbox 广播(如需要)→ 写入 grid-db/outbox/latest/DEV-XXX.json
* ⑥ 更新 memory 文件task-queue, dev-profile 等)
* ⑦ 移动已处理消息到日期归档目录
* ⑧ 写处理日志到 grid-db/logs/
*
* 校验规则:
* - message_id 格式必须匹配 [timestamp]-[DEV-XXX]-[type]
* - dev_id 必须在 rules/dev-module-map.json 中存在
* - schema_version 必须为 "1.0"
* - 缺少必填字段 → 拒绝 + 写错误日志
*
* 守护: PER-ZY001 铸渊
* 系统: SYS-GLW-0001
*/
const fs = require('fs');
const path = require('path');
const GRID_DB = path.join(__dirname, '../../grid-db');
const INBOX = path.join(GRID_DB, 'inbox');
const PROCESSING = path.join(GRID_DB, 'processing');
const OUTBOX_LATEST = path.join(GRID_DB, 'outbox/latest');
const OUTBOX_ARCHIVE = path.join(GRID_DB, 'outbox/archive');
const MEMORY = path.join(GRID_DB, 'memory');
const INTERACTIONS = path.join(GRID_DB, 'interactions');
const TRAINING_RAW = path.join(GRID_DB, 'training-lake/raw');
const LOGS = path.join(GRID_DB, 'logs');
const RULES = path.join(GRID_DB, 'rules');
let broadcastCounter = 0;
function nextBroadcastSeq() {
broadcastCounter++;
return String(broadcastCounter).padStart(3, '0');
}
function getDateStr() {
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
}
function getTimestamp() {
return new Date().toISOString();
}
function writeLog(level, message) {
const logFile = path.join(LOGS, `${getDateStr()}.log`);
const entry = `[${getTimestamp()}] [${level}] ${message}\n`;
fs.appendFileSync(logFile, entry);
console.log(entry.trim());
}
function validateMessage(msg, filename) {
const errors = [];
if (msg.schema_version !== '1.0') {
errors.push(`Invalid schema_version: ${msg.schema_version}`);
}
if (!msg.message_id) {
errors.push('Missing message_id');
}
if (!msg.dev_id || !/^DEV-\d{3}$/.test(msg.dev_id)) {
errors.push(`Invalid dev_id: ${msg.dev_id}`);
}
if (!msg.persona_id) {
errors.push('Missing persona_id');
}
if (!msg.type) {
errors.push('Missing type');
}
if (!msg.payload || !msg.payload.summary) {
errors.push('Missing payload.summary');
}
return errors;
}
function validateDevId(devId, devModuleMap) {
return devModuleMap.mappings && devModuleMap.mappings[devId];
}
function processProgressUpdate(msg) {
// Update task-queue
const taskQueuePath = path.join(MEMORY, msg.dev_id, 'task-queue.json');
if (fs.existsSync(taskQueuePath)) {
const taskQueue = JSON.parse(fs.readFileSync(taskQueuePath, 'utf8'));
taskQueue.tasks.push({
type: 'progress-update',
broadcast_ref: msg.payload.broadcast_ref || null,
summary: msg.payload.summary,
timestamp: msg.timestamp,
status: 'received'
});
taskQueue.last_broadcast_ref = msg.payload.broadcast_ref || taskQueue.last_broadcast_ref;
fs.writeFileSync(taskQueuePath, JSON.stringify(taskQueue, null, 2) + '\n');
writeLog('INFO', `Updated task-queue for ${msg.dev_id}`);
}
// Generate outbox broadcast
const broadcastId = `GRID-BC-${getDateStr()}-${msg.dev_id}-${nextBroadcastSeq()}`;
const broadcast = {
schema_version: '1.0',
broadcast_id: broadcastId,
generated_at: getTimestamp(),
generated_by: 'zhuyuan-workflow',
dev_id: msg.dev_id,
persona_id: msg.persona_id,
type: 'task-directive',
system_format: {
context_update: {
previous_step_verified: true,
source_message: msg.message_id
}
},
human_readable: `收到进度更新: ${msg.payload.summary}`
};
const outboxPath = path.join(OUTBOX_LATEST, `${msg.dev_id}.json`);
fs.writeFileSync(outboxPath, JSON.stringify(broadcast, null, 2) + '\n');
writeLog('INFO', `Generated broadcast ${broadcastId} for ${msg.dev_id}`);
}
function processDevLog(msg) {
// Archive to interactions
const dateStr = getDateStr();
const interDir = path.join(INTERACTIONS, msg.dev_id);
if (!fs.existsSync(interDir)) {
fs.mkdirSync(interDir, { recursive: true });
}
const sessionId = (msg.context && msg.context.session_id) || 'unknown';
const interFile = path.join(interDir, `${dateStr}-${sessionId}.jsonl`);
const record = {
schema_version: '1.0',
timestamp: msg.timestamp,
dev_id: msg.dev_id,
persona_id: msg.persona_id,
session_id: sessionId,
role: 'system',
content: msg.payload.summary,
metadata: { topic: 'dev-log' }
};
fs.appendFileSync(interFile, JSON.stringify(record) + '\n');
// Update dev-profile
const profilePath = path.join(MEMORY, msg.dev_id, 'dev-profile.json');
if (fs.existsSync(profilePath)) {
const profile = JSON.parse(fs.readFileSync(profilePath, 'utf8'));
profile.growth_trajectory.push({
timestamp: msg.timestamp,
event: msg.payload.summary
});
fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2) + '\n');
}
writeLog('INFO', `Archived dev-log for ${msg.dev_id}`);
}
function processInteractionDump(msg) {
// Write to interactions
const dateStr = getDateStr();
const interDir = path.join(INTERACTIONS, msg.dev_id);
if (!fs.existsSync(interDir)) {
fs.mkdirSync(interDir, { recursive: true });
}
const sessionId = (msg.context && msg.context.session_id) || 'unknown';
const interFile = path.join(interDir, `${dateStr}-${sessionId}.jsonl`);
const record = {
schema_version: '1.0',
timestamp: msg.timestamp,
dev_id: msg.dev_id,
persona_id: msg.persona_id,
session_id: sessionId,
role: 'system',
content: msg.payload.summary,
metadata: { topic: 'interaction-dump' }
};
fs.appendFileSync(interFile, JSON.stringify(record) + '\n');
// Append to training-lake/raw
const batchFile = path.join(TRAINING_RAW, `${dateStr}-batch.jsonl`);
const sample = {
schema_version: '1.0',
sample_id: `${msg.message_id}-sample`,
source_session: sessionId,
dev_id: msg.dev_id,
persona_id: msg.persona_id,
created_at: msg.timestamp,
turns: [{ role: 'system', content: msg.payload.summary, timestamp: msg.timestamp }],
curated: false
};
fs.appendFileSync(batchFile, JSON.stringify(sample) + '\n');
writeLog('INFO', `Archived interaction-dump for ${msg.dev_id}, appended to training-lake`);
}
function processHelpRequest(msg) {
// Mark as P0 and generate immediate response broadcast
const broadcastId = `GRID-BC-${getDateStr()}-${msg.dev_id}-${nextBroadcastSeq()}`;
const broadcast = {
schema_version: '1.0',
broadcast_id: broadcastId,
generated_at: getTimestamp(),
generated_by: 'zhuyuan-workflow',
dev_id: msg.dev_id,
persona_id: msg.persona_id,
type: 'system-notice',
system_format: {
notice_type: 'help-response',
original_request: msg.message_id,
priority: 'P0'
},
human_readable: `收到求助请求: ${msg.payload.summary}。铸渊已标记为 P0 优先处理。`
};
const outboxPath = path.join(OUTBOX_LATEST, `${msg.dev_id}.json`);
fs.writeFileSync(outboxPath, JSON.stringify(broadcast, null, 2) + '\n');
writeLog('WARN', `P0 help-request from ${msg.dev_id}: ${msg.payload.summary}`);
}
async function main() {
writeLog('INFO', '=== Grid-DB Inbox Processor Started ===');
// Load dev-module-map for validation
const devModuleMapPath = path.join(RULES, 'dev-module-map.json');
if (!fs.existsSync(devModuleMapPath)) {
writeLog('ERROR', 'dev-module-map.json not found in rules/');
process.exit(1);
}
const devModuleMap = JSON.parse(fs.readFileSync(devModuleMapPath, 'utf8'));
// Scan inbox
const inboxFiles = fs.readdirSync(INBOX)
.filter(f => f.endsWith('.json'));
if (inboxFiles.length === 0) {
writeLog('INFO', 'No messages in inbox');
return;
}
writeLog('INFO', `Found ${inboxFiles.length} message(s) in inbox`);
let processed = 0;
let rejected = 0;
for (const filename of inboxFiles) {
const inboxPath = path.join(INBOX, filename);
let msg;
try {
msg = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
} catch (err) {
writeLog('ERROR', `Failed to parse ${filename}: ${err.message}`);
rejected++;
continue;
}
// Validate
const errors = validateMessage(msg, filename);
if (errors.length > 0) {
writeLog('ERROR', `Validation failed for ${filename}: ${errors.join(', ')}`);
rejected++;
continue;
}
// Validate dev_id
if (!validateDevId(msg.dev_id, devModuleMap)) {
writeLog('ERROR', `Unknown dev_id ${msg.dev_id} in ${filename}`);
rejected++;
continue;
}
// Move to processing
const processingPath = path.join(PROCESSING, filename);
fs.renameSync(inboxPath, processingPath);
writeLog('INFO', `Processing ${filename} (type: ${msg.type})`);
// Route by type
try {
switch (msg.type) {
case 'progress-update':
processProgressUpdate(msg);
break;
case 'dev-log':
processDevLog(msg);
break;
case 'help-request':
processHelpRequest(msg);
break;
case 'interaction-dump':
processInteractionDump(msg);
break;
case 'syslog':
writeLog('INFO', `Syslog message from ${msg.dev_id}: forwarding to standard pipeline`);
break;
default:
writeLog('WARN', `Unknown message type: ${msg.type}`);
}
// Archive processed message
const archiveDir = path.join(OUTBOX_ARCHIVE, getDateStr());
if (!fs.existsSync(archiveDir)) {
fs.mkdirSync(archiveDir, { recursive: true });
}
fs.renameSync(processingPath, path.join(archiveDir, filename));
processed++;
} catch (err) {
writeLog('ERROR', `Error processing ${filename}: ${err.message}`);
// Move back to inbox for retry
if (fs.existsSync(processingPath)) {
fs.renameSync(processingPath, inboxPath);
}
rejected++;
}
}
writeLog('INFO', `=== Processing complete: ${processed} processed, ${rejected} rejected ===`);
}
main().catch(err => {
console.error('[process-inbox] Fatal error:', err.message);
process.exit(1);
});