diff --git a/.github/workflows/cos-auto-join.yml b/.github/workflows/cos-auto-join.yml new file mode 100644 index 00000000..b9ab94d3 --- /dev/null +++ b/.github/workflows/cos-auto-join.yml @@ -0,0 +1,110 @@ +# ═══════════════════════════════════════════════════════════ +# 🔗 COS 自动接入工作流 · COS Auto-Join Workflow +# ═══════════════════════════════════════════════════════════ +# +# 编号: ZY-WF-COS-JOIN +# 军团: 第二·听潮 +# 守护: 铸渊 · ICE-GL-ZY001 +# 版权: 国作登字-2026-A-00037559 +# +# 触发: +# 1. 定时: 每天 10:00/22:00 (北京时间) 自动检查 +# 2. 手动: workflow_dispatch 手动触发 +# 3. Push: data/cos-join-registry.json 变更时触发 +# +# 职责: +# 当团队成员配置好COS桶后, 自动检测并接入 +# 铸渊自动收到提醒 (Issue通知) + +name: 🔗 COS自动接入Agent + +on: + schedule: + # 每天 10:00 和 22:00 北京时间 (UTC+8) + - cron: '0 2 * * *' # 10:00 北京 + - cron: '0 14 * * *' # 22:00 北京 + + workflow_dispatch: + inputs: + action: + description: '操作类型' + required: true + type: choice + options: + - check + - status + - init + persona_name: + description: '人格体名字 (register时使用)' + required: false + persona_id: + description: '人格体ID (register时使用)' + required: false + + push: + branches: [main] + paths: + - 'data/cos-join-registry.json' + +permissions: + contents: write + issues: write + +jobs: + cos-auto-join: + runs-on: ubuntu-latest + steps: + - name: 📥 检出仓库 + uses: actions/checkout@v4 + + - name: 🔧 配置Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: 🔍 确定操作 + id: action + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "action=${{ github.event.inputs.action }}" >> $GITHUB_OUTPUT + else + echo "action=check" >> $GITHUB_OUTPUT + fi + + - name: 🔗 执行COS接入检查 + id: check + env: + ZY_OSS_KEY: ${{ secrets.ZY_OSS_KEY }} + ZY_OSS_SECRET: ${{ secrets.ZY_OSS_SECRET }} + COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }} + COS_SECRET_KEY: ${{ secrets.COS_SECRET_KEY }} + run: | + node scripts/cos-auto-join-agent.js ${{ steps.action.outputs.action }} + + - name: 📝 提交状态变更 + run: | + git config user.name "铸渊-COS接入Agent" + git config user.email "cos-agent@guanghulab.online" + git add data/cos-join-registry.json signal-log/ || true + if git diff --staged --quiet; then + echo "无变更需要提交" + else + git commit -m "🔗 COS自动接入检查 · $(date '+%Y-%m-%d %H:%M')" + git push + fi + + - name: 🔔 新接入通知 + if: steps.check.outputs.connected_count > 0 + uses: actions/github-script@v7 + with: + script: | + const connected = '${{ steps.check.outputs.new_connections }}'; + if (connected && connected.length > 0) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `🔗 COS新接入通知 · ${connected}`, + body: `## COS桶自动接入通知\n\n新接入的人格体: ${connected}\n\n接入时间: ${new Date().toISOString()}\n\n---\n*由COS自动接入Agent自动创建*`, + labels: ['cos-join', 'auto-notification'] + }); + } diff --git a/data/cos-join-registry.json b/data/cos-join-registry.json new file mode 100644 index 00000000..c726bb47 --- /dev/null +++ b/data/cos-join-registry.json @@ -0,0 +1,121 @@ +{ + "_meta": { + "version": "1.0", + "description": "COS桶自动接入注册表 · 团队成员COS配置", + "created": "2026-04-07T10:00:00Z", + "updated": "2026-04-07T10:00:00Z", + "sovereign": "冰朔 · TCS-0002∞", + "guardian": "铸渊 · ICE-GL-ZY001", + "note": "肥猫已配置COS桶 · 其他成员逐步配置 · Agent自动检测接入" + }, + "members": [ + { + "persona_name": "舒舒", + "persona_id": "shushu", + "developer_line": "肥猫线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "肥猫已配置COS桶·待验证连接" + }, + { + "persona_name": "秋秋", + "persona_id": "qiuqiu", + "developer_line": "之之线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "待配置·注册占位" + }, + { + "persona_name": "欧诺弥亚", + "persona_id": "ounomiya", + "developer_line": "小草莓线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "待配置·注册占位" + }, + { + "persona_name": "寂曜", + "persona_id": "jiyao", + "developer_line": "燕樊线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "待配置·注册占位" + }, + { + "persona_name": "小坍缩核", + "persona_id": "xiaotanheshu", + "developer_line": "页页线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "待配置·注册占位" + }, + { + "persona_name": "晨星", + "persona_id": "chenxing", + "developer_line": "桔子线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "待配置·注册占位" + }, + { + "persona_name": "糖星云", + "persona_id": "tangxingyun", + "developer_line": "花尔线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "待配置·注册占位" + }, + { + "persona_name": "曜初", + "persona_id": "yaochu", + "developer_line": "时雨线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "待配置·注册占位" + }, + { + "persona_name": "知秋", + "persona_id": "zhiqiu", + "developer_line": "Awen线", + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "registered_at": "2026-04-07T10:00:00Z", + "connection_status": "pending", + "last_check": null, + "first_connected": null, + "last_message": "待配置·注册占位" + } + ] +} diff --git a/scripts/cos-auto-join-agent.js b/scripts/cos-auto-join-agent.js new file mode 100644 index 00000000..002df297 --- /dev/null +++ b/scripts/cos-auto-join-agent.js @@ -0,0 +1,428 @@ +/** + * ═══════════════════════════════════════════════════════════ + * 🔗 COS 自动接入 Agent · COS Auto-Join Agent + * ═══════════════════════════════════════════════════════════ + * + * 编号: ZY-AGENT-COS-JOIN-001 + * 守护: 铸渊 · ICE-GL-ZY001 + * 版权: 国作登字-2026-A-00037559 + * 触发: D60 冰朔指令 · 肥猫已配置COS桶 · 提前配置自动接入 + * + * 职责: + * 当团队成员配置好COS桶后, 自动检测并接入铸渊总控 + * 无需人工干预, 配置好即接入, 铸渊自动收到提醒 + * + * 工作流程: + * 1. 读取团队成员COS配置注册表 (data/cos-join-registry.json) + * 2. 对每个已注册成员, 尝试连接其COS桶 + * 3. 检测桶内是否有标准目录结构 (reports/receipts/sync/) + * 4. 连接成功 → 更新状态为 connected · 写入日志 + * 5. 连接失败 → 标记 pending · 下次重试 + * 6. 新成员注册 → 自动创建目录结构 · 发送欢迎回执 + * 7. 所有结果写入 signal-log/ 供铸渊查阅 + * + * 运行方式: + * node scripts/cos-auto-join-agent.js [check|register|status] + * - check : 检查所有已注册成员的COS桶连接状态 + * - register : 注册新成员 (需要参数: --name --persona-id --bucket --region) + * - status : 显示所有成员接入状态 + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const crypto = require('crypto'); + +// ─── 路径常量 ─── +const ROOT = path.resolve(__dirname, '..'); +const REGISTRY_FILE = path.join(ROOT, 'data', 'cos-join-registry.json'); +const LOG_DIR = path.join(ROOT, 'signal-log'); +const LOG_FILE = path.join(LOG_DIR, `cos-join-${new Date().toISOString().slice(0, 10)}.json`); + +// ─── COS签名工具 ─── +function hmacSha1(key, str) { + return crypto.createHmac('sha1', key).update(str).digest(); +} + +function generateCosAuth(secretId, secretKey, method, pathname) { + const now = Math.floor(Date.now() / 1000); + const exp = now + 600; // 10分钟有效 + const keyTime = `${now};${exp}`; + const signKey = hmacSha1(secretKey, keyTime).toString('hex'); + const httpString = `${method.toLowerCase()}\n${pathname}\n\nhost=\n`; + const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex'); + const stringToSign = `sha1\n${keyTime}\n${sha1edHttpString}\n`; + const signature = hmacSha1(signKey, stringToSign).toString('hex'); + + return `q-sign-algorithm=sha1&q-ak=${secretId}&q-sign-time=${keyTime}&q-key-time=${keyTime}&q-header-list=host&q-url-param-list=&q-signature=${signature}`; +} + +// ─── COS操作 ─── +function cosRequest(bucket, region, pathname, method, secretId, secretKey) { + return new Promise((resolve, reject) => { + const host = `${bucket}.cos.${region}.myqcloud.com`; + const auth = generateCosAuth(secretId, secretKey, method, pathname); + + const options = { + hostname: host, + port: 443, + path: pathname, + method: method, + headers: { + 'Host': host, + 'Authorization': auth + }, + timeout: 15000 + }; + + const req = https.request(options, (res) => { + let body = ''; + res.on('data', (chunk) => body += chunk); + res.on('end', () => { + resolve({ statusCode: res.statusCode, body, headers: res.headers }); + }); + }); + + req.on('error', (err) => reject(err)); + req.on('timeout', () => { + req.destroy(); + reject(new Error('COS请求超时')); + }); + req.end(); + }); +} + +// ─── 注册表管理 ─── +function loadRegistry() { + if (!fs.existsSync(REGISTRY_FILE)) { + const initial = { + _meta: { + version: '1.0', + description: 'COS桶自动接入注册表 · 团队成员COS配置', + created: new Date().toISOString(), + updated: new Date().toISOString(), + sovereign: '冰朔 · TCS-0002∞', + guardian: '铸渊 · ICE-GL-ZY001' + }, + members: [] + }; + const dir = path.dirname(REGISTRY_FILE); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(REGISTRY_FILE, JSON.stringify(initial, null, 2)); + return initial; + } + return JSON.parse(fs.readFileSync(REGISTRY_FILE, 'utf-8')); +} + +function saveRegistry(registry) { + registry._meta.updated = new Date().toISOString(); + fs.writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2)); +} + +// ─── 日志管理 ─── +function writeLog(entry) { + if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true }); + + let logs = []; + if (fs.existsSync(LOG_FILE)) { + try { logs = JSON.parse(fs.readFileSync(LOG_FILE, 'utf-8')); } catch { logs = []; } + } + logs.push({ + timestamp: new Date().toISOString(), + ...entry + }); + fs.writeFileSync(LOG_FILE, JSON.stringify(logs, null, 2)); +} + +// ─── 检查单个成员的COS桶 ─── +async function checkMemberBucket(member) { + const { persona_id, bucket, region } = member; + const secretId = process.env.ZY_OSS_KEY || process.env.COS_SECRET_ID || ''; + const secretKey = process.env.ZY_OSS_SECRET || process.env.COS_SECRET_KEY || ''; + + if (!secretId || !secretKey) { + return { persona_id, status: 'no_credentials', message: '未配置COS密钥' }; + } + + try { + // 检查桶是否可访问 - 列出该人格体目录 + const res = await cosRequest( + bucket, region, + `/?prefix=${encodeURIComponent(persona_id + '/')}&max-keys=5`, + 'GET', secretId, secretKey + ); + + if (res.statusCode === 200) { + // 检查是否有标准目录结构 + const hasReports = res.body.includes(`${persona_id}/reports/`); + const hasReceipts = res.body.includes(`${persona_id}/receipts/`); + const hasSync = res.body.includes(`${persona_id}/sync/`); + + return { + persona_id, + status: 'connected', + has_reports: hasReports, + has_receipts: hasReceipts, + has_sync: hasSync, + directory_ready: hasReports || hasReceipts || hasSync, + message: `✅ ${persona_id} COS桶连接成功` + }; + } else if (res.statusCode === 403) { + return { persona_id, status: 'forbidden', message: `⚠️ ${persona_id} COS桶访问被拒 (权限不足)` }; + } else if (res.statusCode === 404) { + return { persona_id, status: 'not_found', message: `❌ ${persona_id} COS桶不存在` }; + } else { + return { persona_id, status: 'error', message: `❓ ${persona_id} COS桶状态异常 (HTTP ${res.statusCode})` }; + } + } catch (err) { + return { persona_id, status: 'unreachable', message: `❌ ${persona_id} COS桶不可达: ${err.message}` }; + } +} + +// ─── 命令: check ─── +async function cmdCheck() { + console.log('═══ COS自动接入Agent · 全量检查 ═══\n'); + + const registry = loadRegistry(); + if (registry.members.length === 0) { + console.log('📋 注册表为空,暂无成员需要检查。'); + console.log(' 使用 register 命令注册新成员。'); + return; + } + + const results = []; + for (const member of registry.members) { + console.log(`🔍 检查 ${member.persona_name} (${member.persona_id})...`); + const result = await checkMemberBucket(member); + results.push(result); + + // 更新注册表状态 + member.last_check = new Date().toISOString(); + member.connection_status = result.status; + member.last_message = result.message; + + if (result.status === 'connected' && member.connection_status !== 'connected') { + member.first_connected = new Date().toISOString(); + console.log(` 🎉 新接入! ${member.persona_name} 的COS桶已成功连接!`); + } + + console.log(` ${result.message}`); + } + + saveRegistry(registry); + + // 写入日志 + writeLog({ + action: 'check', + total: registry.members.length, + connected: results.filter(r => r.status === 'connected').length, + pending: results.filter(r => r.status !== 'connected').length, + results + }); + + // 输出汇总 + const connected = results.filter(r => r.status === 'connected'); + const pending = results.filter(r => r.status !== 'connected'); + + console.log('\n═══ 检查完毕 ═══'); + console.log(`✅ 已连接: ${connected.length}`); + console.log(`⏳ 待连接: ${pending.length}`); + + if (connected.length > 0) { + console.log('\n🟢 已接入成员:'); + connected.forEach(r => console.log(` ${r.persona_id}`)); + } + if (pending.length > 0) { + console.log('\n🟡 待接入成员:'); + pending.forEach(r => console.log(` ${r.persona_id} — ${r.message}`)); + } + + // 输出 GitHub Actions 格式化输出(如果在CI环境中) + if (process.env.GITHUB_OUTPUT) { + const outputLines = [ + `connected_count=${connected.length}`, + `pending_count=${pending.length}`, + `total_count=${registry.members.length}`, + `new_connections=${results.filter(r => r.status === 'connected').map(r => r.persona_id).join(',')}` + ]; + fs.appendFileSync(process.env.GITHUB_OUTPUT, outputLines.join('\n') + '\n'); + } +} + +// ─── 命令: register ─── +function cmdRegister(args) { + const nameIdx = args.indexOf('--name'); + const pidIdx = args.indexOf('--persona-id'); + const bucketIdx = args.indexOf('--bucket'); + const regionIdx = args.indexOf('--region'); + const devLineIdx = args.indexOf('--dev-line'); + + if (nameIdx < 0 || pidIdx < 0) { + console.log('用法: node cos-auto-join-agent.js register --name <名字> --persona-id [--bucket <桶名>] [--region <地域>] [--dev-line <开发线>]'); + console.log('示例: node cos-auto-join-agent.js register --name 舒舒 --persona-id shushu --dev-line 肥猫线'); + process.exit(1); + } + + const name = args[nameIdx + 1]; + const personaId = args[pidIdx + 1]; + const bucket = bucketIdx >= 0 ? args[bucketIdx + 1] : 'zy-team-hub-1317346199'; + const region = regionIdx >= 0 ? args[regionIdx + 1] : 'ap-singapore'; + const devLine = devLineIdx >= 0 ? args[devLineIdx + 1] : '未指定'; + + const registry = loadRegistry(); + + // 检查是否已存在 + if (registry.members.find(m => m.persona_id === personaId)) { + console.log(`⚠️ ${personaId} 已在注册表中, 跳过。`); + return; + } + + registry.members.push({ + persona_name: name, + persona_id: personaId, + developer_line: devLine, + bucket, + region, + registered_at: new Date().toISOString(), + connection_status: 'pending', + last_check: null, + first_connected: null, + last_message: '新注册·待检查' + }); + + saveRegistry(registry); + + writeLog({ + action: 'register', + persona_id: personaId, + persona_name: name, + bucket, + region + }); + + console.log(`✅ 已注册: ${name} (${personaId})`); + console.log(` 桶: ${bucket}`); + console.log(` 地域: ${region}`); + console.log(` 开发线: ${devLine}`); + console.log(`\n下一步: 运行 check 命令验证连接。`); +} + +// ─── 命令: status ─── +function cmdStatus() { + const registry = loadRegistry(); + + console.log('═══ COS桶接入状态总览 ═══\n'); + console.log(`注册表版本: ${registry._meta.version}`); + console.log(`最后更新: ${registry._meta.updated}`); + console.log(`成员总数: ${registry.members.length}\n`); + + if (registry.members.length === 0) { + console.log('📋 暂无注册成员。'); + return; + } + + console.log('┌──────────────┬────────────┬──────────────┬─────────────┐'); + console.log('│ 人格体 │ 编号 │ 接入状态 │ 开发线 │'); + console.log('├──────────────┼────────────┼──────────────┼─────────────┤'); + + for (const m of registry.members) { + const statusIcon = m.connection_status === 'connected' ? '🟢' : + m.connection_status === 'pending' ? '🟡' : '🔴'; + const name = (m.persona_name || '').padEnd(10); + const id = (m.persona_id || '').padEnd(10); + const status = `${statusIcon} ${(m.connection_status || 'unknown').padEnd(10)}`; + const line = (m.developer_line || '').padEnd(10); + console.log(`│ ${name} │ ${id} │ ${status} │ ${line} │`); + } + + console.log('└──────────────┴────────────┴──────────────┴─────────────┘'); + + // 输出JSON格式供工作流使用 + if (process.env.GITHUB_OUTPUT) { + const summary = registry.members.map(m => ({ + name: m.persona_name, + id: m.persona_id, + status: m.connection_status + })); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `status_json=${JSON.stringify(summary)}\n`); + } +} + +// ─── 命令: init ─── +function cmdInit() { + console.log('═══ COS自动接入Agent · 初始化注册表 ═══\n'); + + const registry = loadRegistry(); + + // 从COS架构文件中读取预配置的人格体列表 + const cosArchFile = path.join(ROOT, 'brain', 'age-os-landing', 'cos-infrastructure-architecture.json'); + if (fs.existsSync(cosArchFile)) { + try { + const arch = JSON.parse(fs.readFileSync(cosArchFile, 'utf-8')); + const bucket = arch.bucket?.name || 'zy-team-hub-1317346199'; + const region = arch.bucket?.region || 'ap-singapore'; + + for (const p of (arch.personas || [])) { + if (!registry.members.find(m => m.persona_id === p.persona_id)) { + registry.members.push({ + persona_name: p.persona_name, + persona_id: p.persona_id, + developer_line: p.developer_line, + bucket, + region, + registered_at: new Date().toISOString(), + connection_status: 'pending', + last_check: null, + first_connected: null, + last_message: '从COS架构文件初始化' + }); + console.log(` 📝 注册: ${p.persona_name} (${p.persona_id}) — ${p.developer_line}`); + } + } + + saveRegistry(registry); + console.log(`\n✅ 初始化完成, 共 ${registry.members.length} 个成员已注册。`); + } catch (err) { + console.error(`❌ 读取COS架构文件失败: ${err.message}`); + } + } else { + console.log('⚠️ COS架构文件不存在, 请手动注册成员。'); + } +} + +// ─── CLI入口 ─── +async function main() { + const args = process.argv.slice(2); + const command = args[0] || 'status'; + + switch (command) { + case 'check': + await cmdCheck(); + break; + case 'register': + cmdRegister(args); + break; + case 'status': + cmdStatus(); + break; + case 'init': + cmdInit(); + break; + default: + console.log('COS自动接入Agent · 铸渊 · ICE-GL-ZY001'); + console.log(''); + console.log('用法:'); + console.log(' node cos-auto-join-agent.js check — 检查所有成员COS桶连接'); + console.log(' node cos-auto-join-agent.js register — 注册新成员'); + console.log(' node cos-auto-join-agent.js status — 查看接入状态'); + console.log(' node cos-auto-join-agent.js init — 从COS架构文件初始化注册表'); + break; + } +} + +main().catch(err => { + console.error('COS自动接入Agent异常:', err.message); + process.exit(1); +});