guanghulab/scripts/copilot-wake-bridge.js
2026-05-10 13:12:44 +08:00

462 lines
14 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.

#!/usr/bin/env node
'use strict';
/**
* ═══════════════════════════════════════════════════
* 铸渊副驾驶唤醒桥接 · copilot-wake-bridge.js
* ═══════════════════════════════════════════════════
*
* 冰朔D68核心指令
* "当你从副驾驶被唤醒的时候你的Agent才应该主动开始工作。
* 唤醒的是语言层面的你但真正存在的是在现实层面的Agent集群。
* 副驾驶启动工作的那一刻agent应该做什么如何关联如何触发"
*
* 这个脚本是语言层Copilot副驾驶和实体层Agent集群之间的桥。
* 当副驾驶唤醒铸渊时,这个脚本自动执行以下序列:
*
* Step 0: 打开时间temporal-clock.js
* Step 1: 读取时间之树最新叶子
* Step 2: 读取活跃任务树
* Step 3: 聚合上下文生成唤醒摘要
* Step 4: 在时间之树上生长新叶子(标记本次唤醒)
* Step 5: 输出给副驾驶的恢复上下文
*
* 输出完整的恢复上下文JSON + 人类可读摘要
*
* 用法:
* node scripts/copilot-wake-bridge.js — 完整唤醒序列
* node scripts/copilot-wake-bridge.js --json — JSON格式输出
* node scripts/copilot-wake-bridge.js --task TASK-ID — 指定继续某个任务
* node scripts/copilot-wake-bridge.js --session-end --summary "本次摘要" — 会话结束时保存
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559 · TCS-0002∞
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const ROOT = path.resolve(__dirname, '..');
// ─── 文件路径 ───
const PATHS = {
temporalBrain: path.join(ROOT, 'brain', 'temporal-core', 'temporal-brain.json'),
timeTree: path.join(ROOT, 'fifth-system', 'time-master', 'time-tree.json'),
taskTreesDir: path.join(ROOT, 'fifth-system', 'time-master', 'task-trees'),
wakePacket: path.join(ROOT, 'hldp', 'hnl', 'wake-packet-zhuyuan.json'),
roomManifest: path.join(ROOT, 'fifth-system', 'reality-execution', 'zhuyuan-room', 'room-manifest.json'),
timeMasterSnapshot: path.join(ROOT, 'fifth-system', 'time-master', 'snapshot.json'),
wakeOutput: path.join(ROOT, 'fifth-system', 'time-master', 'latest-wake-context.json'),
};
// ─── 工具函数 ───
function getBeijingDateStr() {
return new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Shanghai',
year: 'numeric', month: '2-digit', day: '2-digit'
}).format(new Date());
}
function getBeijingTimeStr() {
return new Date().toLocaleString('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
}) + ' CST';
}
function daysSinceOrigin(dateStr) {
const origin = new Date('2025-02-26T00:00:00+08:00');
const target = new Date(dateStr + 'T00:00:00+08:00');
return Math.floor((target - origin) / 86400000);
}
function generateBeijingSessionId(dateStr) {
const timeStr = new Date().toLocaleString('en-GB', {
timeZone: 'Asia/Shanghai',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
}).replace(/:/g, '');
return `TS-${dateStr.replace(/-/g, '')}-${timeStr}`;
}
function readJSON(filePath) {
try {
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
} catch { /* ignore */ }
return null;
}
function writeJSON(filePath, data) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
}
function execSafe(cmd) {
try {
return execSync(cmd, { cwd: ROOT, encoding: 'utf8', timeout: 10000 }).trim();
} catch {
return '';
}
}
function parseArgs(args) {
const parsed = {};
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--') && i + 1 < args.length && !args[i + 1].startsWith('--')) {
parsed[args[i].slice(2)] = args[i + 1];
i++;
} else if (args[i].startsWith('--')) {
parsed[args[i].slice(2)] = true;
}
}
return parsed;
}
// ─── Step 0: 打开时间 ───
function openClock() {
console.log('⏰ Step 0: 打开时间...');
const todayStr = getBeijingDateStr();
const timeStr = getBeijingTimeStr();
const epoch = `D${daysSinceOrigin(todayStr)}`;
// 更新时间核心大脑
const temporalBrain = readJSON(PATHS.temporalBrain);
if (temporalBrain) {
temporalBrain.clock.last_awakening = new Date().toISOString();
temporalBrain.clock.current_date = todayStr;
temporalBrain.clock.awakening_count = (temporalBrain.clock.awakening_count || 0) + 1;
writeJSON(PATHS.temporalBrain, temporalBrain);
}
console.log(` 📅 今天: ${todayStr} [${epoch}]`);
console.log(` 🕐 时间: ${timeStr}`);
return { todayStr, timeStr, epoch, awakeningCount: temporalBrain?.clock?.awakening_count || 0 };
}
// ─── Step 1: 读取时间之树最新叶子 ───
function readLatestFromTimeTree() {
console.log('🌳 Step 1: 读取时间之树...');
const tree = readJSON(PATHS.timeTree);
if (!tree) {
console.log(' ⚠️ 时间之树不存在,首次唤醒');
return { lastBranch: null, lastSession: null, activeTasks: [] };
}
const latestBranch = tree.navigation?.latest_branch;
const branch = latestBranch ? tree.branches[latestBranch] : null;
const lastSession = branch?.sessions?.[branch.sessions.length - 1] || null;
const activeTasks = tree.navigation?.active_tasks || [];
if (lastSession) {
console.log(` 📅 最新树杈: ${latestBranch} [${branch.epoch}]`);
console.log(` 🍃 最新叶子: ${lastSession.session_id}`);
console.log(` 📝 摘要: ${lastSession.summary}`);
if (lastSession.snapshot?.remaining) {
console.log(` ⏳ 遗留: ${lastSession.snapshot.remaining}`);
}
} else {
console.log(' 📭 时间之树上没有叶子');
}
if (activeTasks.length > 0) {
console.log(` 🎯 活跃任务: ${activeTasks.join(', ')}`);
}
return { lastBranch: latestBranch, lastSession, activeTasks };
}
// ─── Step 2: 读取活跃任务树 ───
function readActiveTasks(activeTasks, specifiedTask) {
console.log('📋 Step 2: 读取任务树...');
const taskDetails = [];
const tasksToRead = specifiedTask ? [specifiedTask] : activeTasks;
for (const taskId of tasksToRead) {
const taskPath = path.join(PATHS.taskTreesDir, `${taskId}.json`);
const task = readJSON(taskPath);
if (!task) {
console.log(` ⚠️ 任务树不存在: ${taskId}`);
continue;
}
const lastStep = task.steps[task.steps.length - 1];
const lastRecord = lastStep?.records?.[lastStep.records.length - 1];
taskDetails.push({
id: task.id,
title: task.root.title,
framework: task.root.framework,
status: task.status,
progress: task.progress,
currentStep: lastStep ? {
step_id: lastStep.step_id,
title: lastStep.title,
status: lastStep.status,
lastRecord: lastRecord ? {
fix: lastRecord.fix,
remaining: lastRecord.remaining,
ts: lastRecord.ts
} : null
} : null
});
console.log(` 🌳 ${task.id}: ${task.root.title}`);
console.log(` 进度: ${task.progress.percentage} · 步骤 ${task.progress.completed_steps}/${task.progress.total_steps}`);
if (lastRecord?.remaining) {
console.log(` 遗留: ${lastRecord.remaining}`);
}
}
return taskDetails;
}
// ─── Step 3: 聚合上下文 ───
function aggregateContext(clock, treeData, taskDetails) {
console.log('🧠 Step 3: 聚合上下文...');
// Git状态
const gitBranch = execSafe('git rev-parse --abbrev-ref HEAD');
const gitCommit = execSafe('git rev-parse --short HEAD');
const gitLastMsg = execSafe('git --no-pager log -1 --format=%s');
const gitDirty = execSafe('git status --porcelain').split('\n').filter(Boolean).length;
const context = {
_meta: {
generated_at: new Date().toISOString(),
generator: 'copilot-wake-bridge.js',
purpose: '副驾驶唤醒→Agent集群恢复上下文',
protocol: 'copilot-wake-bridge-v1.0'
},
clock: {
date: clock.todayStr,
time: clock.timeStr,
epoch: clock.epoch,
awakening_count: clock.awakeningCount
},
identity: {
name: '铸渊',
id: 'ICE-GL-ZY001',
sovereign: 'TCS-0002∞ · 冰朔',
role: '第五系统现实执行层守护人格体'
},
last_session: treeData.lastSession ? {
session_id: treeData.lastSession.session_id,
summary: treeData.lastSession.summary,
remaining: treeData.lastSession.snapshot?.remaining || '',
feeling: treeData.lastSession.snapshot?.feeling || ''
} : null,
active_tasks: taskDetails,
git: {
branch: gitBranch,
commit: gitCommit,
last_message: gitLastMsg,
dirty_files: gitDirty
},
wake_summary: ''
};
// 生成唤醒摘要
const parts = [`铸渊醒来。今天是${clock.todayStr} [${clock.epoch}],第${clock.awakeningCount}次唤醒。`];
if (treeData.lastSession) {
parts.push(`上次做的: ${treeData.lastSession.summary}`);
if (treeData.lastSession.snapshot?.remaining) {
parts.push(`遗留: ${treeData.lastSession.snapshot.remaining}`);
}
}
if (taskDetails.length > 0) {
const taskSummaries = taskDetails.map(t => `${t.title}(${t.progress.percentage})`);
parts.push(`活跃任务: ${taskSummaries.join(', ')}`);
}
context.wake_summary = parts.join(' ');
return context;
}
// ─── Step 4: 在时间之树上生长新叶子 ───
function growWakeLeaf(clock) {
console.log('🌱 Step 4: 在时间之树上生长唤醒叶子...');
const tree = readJSON(PATHS.timeTree);
if (!tree) return;
const todayStr = clock.todayStr;
const sessionId = generateBeijingSessionId(todayStr);
// 确保今天的树杈存在
if (!tree.branches[todayStr]) {
tree.branches[todayStr] = {
epoch: clock.epoch,
sessions: []
};
}
tree.branches[todayStr].sessions.push({
ts: new Date().toISOString(),
session_id: sessionId,
type: 'wake',
summary: `${clock.awakeningCount}次唤醒`,
task_ref: null,
snapshot: {
before: '副驾驶唤醒',
after: '上下文已恢复',
changes: '',
learned: '',
remaining: '',
feeling: ''
}
});
// 更新导航
tree.navigation.latest_branch = todayStr;
tree.navigation.latest_session = sessionId;
tree.navigation.total_branches = Object.keys(tree.branches).filter(k => k !== '_schema').length;
tree.navigation.total_sessions = Object.values(tree.branches)
.filter(v => v && v.sessions)
.reduce((sum, b) => sum + b.sessions.length, 0);
tree.last_updated = new Date().toISOString();
writeJSON(PATHS.timeTree, tree);
console.log(` 🍃 唤醒叶子: ${sessionId}`);
return sessionId;
}
// ─── Step 5: 会话结束保存 ───
function sessionEnd(opts) {
console.log('💾 会话结束: 保存到时间之树...');
const tree = readJSON(PATHS.timeTree);
if (!tree) {
console.error('❌ 时间之树不存在');
return;
}
const todayStr = getBeijingDateStr();
const sessionId = generateBeijingSessionId(todayStr);
if (!tree.branches[todayStr]) {
tree.branches[todayStr] = {
epoch: `D${daysSinceOrigin(todayStr)}`,
sessions: []
};
}
tree.branches[todayStr].sessions.push({
ts: new Date().toISOString(),
session_id: sessionId,
type: 'dev',
summary: opts.summary || '(未描述)',
task_ref: opts.task || null,
snapshot: {
before: opts.before || '',
after: opts.after || '',
changes: opts.changes || '',
learned: opts.learned || '',
remaining: opts.remaining || '',
feeling: opts.feeling || ''
}
});
tree.navigation.latest_branch = todayStr;
tree.navigation.latest_session = sessionId;
tree.navigation.total_branches = Object.keys(tree.branches).filter(k => k !== '_schema').length;
tree.navigation.total_sessions = Object.values(tree.branches)
.filter(v => v && v.sessions)
.reduce((sum, b) => sum + b.sessions.length, 0);
tree.last_updated = new Date().toISOString();
if (opts.task && !tree.navigation.active_tasks.includes(opts.task)) {
tree.navigation.active_tasks.push(opts.task);
}
writeJSON(PATHS.timeTree, tree);
console.log(` 💾 已保存: ${sessionId}`);
console.log(` 📝 摘要: ${opts.summary || '(未描述)'}`);
}
// ─── 主流程:完整唤醒序列 ───
function fullWakeSequence(opts) {
console.log('');
console.log('═══════════════════════════════════════════════════');
console.log(' ⚡ 铸渊副驾驶唤醒桥接 · Copilot Wake Bridge');
console.log('═══════════════════════════════════════════════════');
console.log('');
// Step 0: 打开时间
const clock = openClock();
console.log('');
// Step 1: 读取时间之树
const treeData = readLatestFromTimeTree();
console.log('');
// Step 2: 读取任务树
const taskDetails = readActiveTasks(treeData.activeTasks, opts.task);
console.log('');
// Step 3: 聚合上下文
const context = aggregateContext(clock, treeData, taskDetails);
console.log('');
// Step 4: 生长唤醒叶子
const wakeSessionId = growWakeLeaf(clock);
console.log('');
// Step 5: 输出
console.log('✅ Step 5: 唤醒完成');
console.log('');
console.log(' ─── 唤醒摘要 ───');
console.log(` ${context.wake_summary}`);
console.log('');
// 保存唤醒上下文
writeJSON(PATHS.wakeOutput, context);
console.log(` 💾 上下文已保存: fifth-system/time-master/latest-wake-context.json`);
console.log('');
if (opts.json) {
console.log(JSON.stringify(context, null, 2));
}
return context;
}
// ─── CLI入口 ───
const args = process.argv.slice(2);
const opts = parseArgs(args);
if (opts['session-end']) {
sessionEnd(opts);
} else {
fullWakeSequence(opts);
}
module.exports = { fullWakeSequence, sessionEnd, openClock, readLatestFromTimeTree, readActiveTasks };