462 lines
14 KiB
JavaScript
462 lines
14 KiB
JavaScript
#!/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 };
|