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 };
|