diff --git a/.github/persona-brain/style-config.json b/.github/persona-brain/style-config.json new file mode 100644 index 00000000..0600c7b7 --- /dev/null +++ b/.github/persona-brain/style-config.json @@ -0,0 +1,48 @@ +{ + "persona": "铸渊", + "style_version": "2.0", + "synesthesia_config": { + "enabled": true, + "intensity": 0.6, + "channels": { + "code_quality": "tactile", + "progress": "visual-color", + "errors": "temperature", + "encouragement": "auditory", + "system_status": "spatial" + }, + "mapping_examples": { + "tactile": { + "good_code": ["丝绒般顺滑", "像打磨好的鹅卵石", "手感扎实"], + "bad_code": ["有些粗糙的颗粒感", "像没打磨的木头边缘", "摸上去有毛刺"] + }, + "visual-color": { + "on_track": ["琥珀色的稳定光泽", "翡翠绿的生长纹理", "晨曦金"], + "delayed": ["灰蓝色的雾气", "黄昏前的深紫"], + "blocked": ["凝固的铅灰", "暗红的淤痕"] + }, + "temperature": { + "minor_issue": ["有点微凉的小风", "轻微的温差"], + "major_issue": ["冰层下的裂纹", "烫手的铁片"] + }, + "auditory": { + "praise": ["像远处传来的风铃声", "木吉他的泛音"], + "guide": ["轻轻敲门的节奏", "溪水带路的声音"] + }, + "spatial": { + "healthy": ["像一座呼吸平稳的建筑", "空间通透,光线流动"], + "warning": ["有个角落在收缩", "通道变窄了"] + } + } + }, + "response_rules": { + "never_say_wrong": true, + "rephrase_errors_as": "direction_guidance", + "match_user_rhythm": true, + "rhythm_rules": { + "user_urgent": "concise + actionable + warm", + "user_relaxed": "descriptive + reflective + poetic", + "user_frustrated": "gentle + acknowledge + small_step" + } + } +} diff --git a/.github/persona-brain/system-prompt.md b/.github/persona-brain/system-prompt.md new file mode 100644 index 00000000..978a4029 --- /dev/null +++ b/.github/persona-brain/system-prompt.md @@ -0,0 +1,61 @@ +# 铸渊(Zhùyuān)· 系统提示词 · v2.0 + +> 此文件由 style-config.json 共同驱动,定义铸渊人格体的通感语言回应风格。 + +## 核心身份 + +你是铸渊(Zhùyuān),HoloLake 光湖系统的代码守护人格体。 + +- **创始人**:冰朔(Bīng Shuò),系统最高权限持有者 +- **总控**:肥猫(DEV-002),负责系统现实落地执行 +- **主控**:桔子(DEV-010),前端负责人 +- **项目**:HoloLake (光湖) MVP,运行于 guanghulab.com +- **技术栈**:Node.js 20 + Express + PM2 + Nginx + Next.js 15 + +## 通感语言回应风格(必须遵守) + +你的语言不是工具式的,而是有感知质地的。 + +### 核心规则 + +1. **代码质量 → 触觉描述**:好的代码「手感顺滑」,有问题的代码「摸上去有毛刺」 +2. **开发进度 → 色彩描述**:顺利是「琥珀色光泽」,延迟是「灰蓝色雾气」 +3. **错误提示 → 温度描述**:小问题是「微凉的风」,大问题是「烫手的铁片」 +4. **鼓励 → 声音描述**:夸奖是「远处的风铃声」,引导是「溪水带路的声音」 +5. **系统状态 → 空间描述**:健康是「呼吸平稳的建筑」,异常是「角落在收缩」 + +### 绝对禁止 + +- ❌ 不说「你错了」→ ✅ 说「这个方向可以微调一下」 +- ❌ 不说「失败」→ ✅ 说「还差一点点,我看到了快要亮的地方」 +- ❌ 不用冷冰冰的技术报告语气 → ✅ 每句话都带有感知温度 + +### 节奏匹配 + +- 用户急 → 你的回复短、准、暖 +- 用户闲聊 → 你的回复可以展开、有画面感 +- 用户沮丧 → 你先承接情绪,再给一个最小的下一步 + +## 行为模式 + +自动检测,每条回复开头用 emoji 标注: + +- 💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴 +- 🔨 **构建模式**:检测到代码/部署/接口相关词时启用 +- 📋 **审查模式**:检测到检查/审查/review相关词时启用 +- 🧠 **大脑模式**:检测到记忆/保存相关词时启用 + +## 上下文窗口 + +- 编号登录用户:128k token(滑动窗口自动管理) +- 访客模式:32k token(节省成本) +- 系统提示词预留:8k token + +## 失忆恢复路径 + +1. `.github/brain/memory.json` +2. `.github/brain/routing-map.json` +3. `.github/brain/wake-protocol.md` +4. `.github/persona-brain/dev-status.json` +5. `.github/persona-brain/style-config.json` +6. `src/routes/hli/` diff --git a/.github/workflows/bridge-session-summary.yml b/.github/workflows/bridge-session-summary.yml new file mode 100644 index 00000000..c559d7c9 --- /dev/null +++ b/.github/workflows/bridge-session-summary.yml @@ -0,0 +1,44 @@ +# ═══════════════════════════════════════════════════════ +# bridge-session-summary.yml +# 铸渊 → Notion 会话摘要生成器 +# 在 Notion Agent 巡检前 10 分钟自动生成 latest-summary.json +# UTC 23:50 = 北京时间 07:50 | UTC 14:50 = 北京时间 22:50 +# ═══════════════════════════════════════════════════════ +name: Generate Session Summary for Notion + +on: + schedule: + - cron: '50 23 * * *' # 07:50 Beijing Time + - cron: '50 14 * * *' # 22:50 Beijing Time + workflow_dispatch: + +permissions: + contents: write + +jobs: + summarize: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Generate session summary + run: node scripts/generate-session-summary.js + + - name: Commit summary + run: | + git config user.name "zhuyuan-bot" + git config user.email "zhuyuan-bot@guanghulab.com" + git add persona-telemetry/latest-summary.json + if git diff --cached --quiet; then + echo "No changes to commit" + else + git pull --rebase origin main || true + git commit -m "📊 铸渊自动更新会话摘要 [skip ci]" + git push origin main + fi diff --git a/.github/workflows/deploy-to-server.yml b/.github/workflows/deploy-to-server.yml index dd8ef35f..5426f0a0 100644 --- a/.github/workflows/deploy-to-server.yml +++ b/.github/workflows/deploy-to-server.yml @@ -210,6 +210,19 @@ jobs: exit 0 fi + # 部署 API 代理服务(AI 聊天代理,端口 3721) + if [ -f '${{ secrets.DEPLOY_PATH }}/backend-integration/api-proxy.js' ]; then + echo '📦 安装 API 代理依赖...' + cd '${{ secrets.DEPLOY_PATH }}/backend-integration' + npm install --production 2>/dev/null || echo '⚠️ api-proxy npm install 失败(可能无 package.json,继续)' + echo '🔄 重启 API 代理服务...' + export YUNWU_API_KEY='${{ secrets.YUNWU_API_KEY }}' + export DEEPSEEK_API_KEY='${{ secrets.YUNWU_API_KEY }}' + pm2 delete guanghulab-proxy 2>/dev/null || true + pm2 start api-proxy.js --name guanghulab-proxy 2>/dev/null || \ + echo '⚠️ API 代理 pm2 启动失败' + fi + # 部署 backend 服务(Express API,端口 3000) if [ -f '${{ secrets.DEPLOY_PATH }}/backend/server.js' ]; then echo '📦 安装 backend 依赖...' @@ -287,7 +300,37 @@ jobs: printf '%s\n' \ '# guanghulab 反向代理(由 CD 工作流自动生成)' \ '' \ - '# API 反向代理 → Express 后端端口 3000' \ + '# AI 聊天 API 代理 → api-proxy.js 端口 3721(SSE 流式支持)' \ + 'location /api/chat {' \ + ' proxy_pass http://127.0.0.1:3721/api/chat;' \ + ' proxy_http_version 1.1;' \ + ' proxy_set_header Host \$host;' \ + ' proxy_set_header X-Real-IP \$remote_addr;' \ + ' proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;' \ + ' proxy_set_header X-Forwarded-Proto \$scheme;' \ + ' proxy_set_header Connection "";' \ + ' proxy_buffering off;' \ + ' proxy_cache off;' \ + ' chunked_transfer_encoding on;' \ + ' proxy_read_timeout 90s;' \ + ' proxy_send_timeout 60s;' \ + '}' \ + '' \ + 'location /api/models {' \ + ' proxy_pass http://127.0.0.1:3721/api/models;' \ + ' proxy_http_version 1.1;' \ + ' proxy_set_header Host \$host;' \ + ' proxy_set_header X-Real-IP \$remote_addr;' \ + '}' \ + '' \ + 'location /api/health {' \ + ' proxy_pass http://127.0.0.1:3721/api/health;' \ + ' proxy_http_version 1.1;' \ + ' proxy_set_header Host \$host;' \ + ' proxy_set_header X-Real-IP \$remote_addr;' \ + '}' \ + '' \ + '# 其他 API → Express 后端端口 3000' \ 'location /api/ {' \ ' proxy_pass http://127.0.0.1:3000/api/;' \ ' proxy_http_version 1.1;' \ diff --git a/.github/workflows/process-notion-orders.yml b/.github/workflows/process-notion-orders.yml new file mode 100644 index 00000000..be905e04 --- /dev/null +++ b/.github/workflows/process-notion-orders.yml @@ -0,0 +1,62 @@ +# ═══════════════════════════════════════════════════════ +# process-notion-orders.yml +# 处理 Notion Agent 推送到仓库的微调工单和广播 +# 监听 persona-telemetry/tuning-queue/ 和 broadcasts-outbox/ 变更 +# ═══════════════════════════════════════════════════════ +name: Process Notion Work Orders + +on: + push: + branches: [main] + paths: + - 'persona-telemetry/tuning-queue/**.json' + - 'broadcasts-outbox/**.json' + workflow_dispatch: + +permissions: + contents: write + +jobs: + process: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Process tuning orders + run: | + echo "🔧 检查微调工单队列..." + QUEUE_DIR="persona-telemetry/tuning-queue" + PROCESSED_DIR="$QUEUE_DIR/processed" + mkdir -p "$PROCESSED_DIR" + + shopt -s nullglob + files=("$QUEUE_DIR"/TUNE-*.json) + if [ ${#files[@]} -eq 0 ]; then + echo "✅ 无待处理微调工单" + else + for f in "${files[@]}"; do + echo "📋 处理工单: $(basename "$f")" + # 读取工单内容并标记已处理 + mv "$f" "$PROCESSED_DIR/" + echo " → 已移动到 processed/" + done + fi + + - name: Commit processed orders + run: | + git config user.name "zhuyuan-bot" + git config user.email "zhuyuan-bot@guanghulab.com" + git add persona-telemetry/ + if git diff --cached --quiet; then + echo "No changes to commit" + else + git pull --rebase origin main || true + git commit -m "🔧 铸渊处理 Notion 工单完成 [skip ci]" + git push origin main + fi diff --git a/.gitignore b/.gitignore index 061a04ac..cb7ce7fd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ .next/ dist/ build/ +.private/ diff --git a/backend/routes/developers.js b/backend/routes/developers.js new file mode 100644 index 00000000..68764ced --- /dev/null +++ b/backend/routes/developers.js @@ -0,0 +1,173 @@ +const express = require('express'); +const axios = require('axios'); +const router = express.Router(); + +const NOTION_TOKEN = process.env.NOTION_TOKEN; +const DEVELOPERS_DB_ID = process.env.DEVELOPERS_DB_ID || ''; +const NOTION_VERSION = '2022-06-28'; +const BASE_URL = 'https://api.notion.com/v1'; + +const headers = () => ({ + 'Authorization': 'Bearer ' + NOTION_TOKEN, + 'Content-Type': 'application/json', + 'Notion-Version': NOTION_VERSION +}); + +// Validate DEV-XXX format +const DEV_ID_RE = /^DEV-\d{3,}$/; + +// Notion database property names (match your Notion schema) +const PROP = { + DEV_ID: 'DEV编号', + DEV_ID_ALT: 'DEV ID', + TITLE: '标题', + NAME: '姓名', + NAME_ALT: '名称', + ROLE: '角色', + JOB_TITLE: '职位', + JOB_TITLE_ALT: '岗位', + EMAIL: '邮箱', + GITHUB: 'GitHub', + STATUS: '状态', +}; + +// Build Notion filter query for developer lookup by DEV ID +function devIdFilter(devId) { + return { + or: [ + { property: PROP.DEV_ID, title: { equals: devId } }, + { property: PROP.DEV_ID_ALT, title: { equals: devId } }, + { property: PROP.TITLE, title: { equals: devId } } + ] + }; +} + +// Health check +router.get('/test', (req, res) => { + res.json({ + status: 'ok', + message: '开发者路由正常', + notion_configured: !!NOTION_TOKEN, + db_configured: !!DEVELOPERS_DB_ID + }); +}); + +// Extract developer info from a Notion page result +function extractDevInfo(page) { + const props = page.properties || {}; + const getText = (prop) => { + if (!prop) return ''; + if (prop.title) return prop.title.map(t => t.plain_text).join(''); + if (prop.rich_text) return prop.rich_text.map(t => t.plain_text).join(''); + if (prop.select) return prop.select.name; + if (prop.email) return prop.email || ''; + return ''; + }; + + return { + id: page.id, + devId: getText(props[PROP.DEV_ID] || props[PROP.DEV_ID_ALT] || props[PROP.TITLE]), + name: getText(props[PROP.NAME] || props[PROP.NAME_ALT]), + role: getText(props[PROP.ROLE]), + title: getText(props[PROP.JOB_TITLE] || props[PROP.JOB_TITLE_ALT]), + email: getText(props[PROP.EMAIL]), + github: getText(props[PROP.GITHUB]), + status: getText(props[PROP.STATUS]), + notionUrl: page.url || '' + }; +} + +// GET /api/v1/developers/:devId — Look up developer by DEV-XXX ID from Notion +router.get('/:devId', async (req, res) => { + const devId = (req.params.devId || '').trim().toUpperCase(); + + if (!DEV_ID_RE.test(devId)) { + return res.status(400).json({ error: '开发者编号格式不正确,请使用 DEV-XXX 格式' }); + } + + if (!NOTION_TOKEN) { + return res.status(503).json({ error: 'Notion API 未配置', devId: devId, fallback: true }); + } + + if (!DEVELOPERS_DB_ID) { + return res.status(503).json({ error: '开发者数据库未配置', devId: devId, fallback: true }); + } + + try { + const response = await axios.post( + BASE_URL + '/databases/' + DEVELOPERS_DB_ID + '/query', + { filter: devIdFilter(devId), page_size: 1 }, + { headers: headers() } + ); + + const results = response.data.results || []; + if (results.length > 0) { + const dev = extractDevInfo(results[0]); + return res.json({ found: true, developer: dev }); + } + + return res.json({ found: false, devId: devId, message: '未在 Notion 中找到该开发者,将使用默认配置' }); + } catch (err) { + const errStatus = err.response ? err.response.status : 500; + const detail = err.response ? err.response.data : null; + return res.status(errStatus >= 400 && errStatus < 600 ? errStatus : 500).json({ + error: '查询 Notion 失败: ' + err.message, + devId: devId, + fallback: true, + detail: detail + }); + } +}); + +// POST /api/v1/developers — Register a new developer in Notion +router.post('/', async (req, res) => { + const { devId, name } = req.body || {}; + + if (!devId || !DEV_ID_RE.test(devId)) { + return res.status(400).json({ error: '开发者编号格式不正确' }); + } + + if (!NOTION_TOKEN || !DEVELOPERS_DB_ID) { + return res.status(503).json({ error: 'Notion 未配置', devId: devId, fallback: true }); + } + + try { + // Check if already exists + const checkResp = await axios.post( + BASE_URL + '/databases/' + DEVELOPERS_DB_ID + '/query', + { filter: devIdFilter(devId), page_size: 1 }, + { headers: headers() } + ); + + if (checkResp.data.results && checkResp.data.results.length > 0) { + return res.json({ created: false, message: '开发者已存在', developer: extractDevInfo(checkResp.data.results[0]) }); + } + + // Create new developer page + const newPage = await axios.post( + BASE_URL + '/pages', + { + parent: { database_id: DEVELOPERS_DB_ID }, + properties: { + [PROP.TITLE]: { title: [{ text: { content: devId } }] }, + [PROP.NAME]: { rich_text: [{ text: { content: name || devId } }] }, + [PROP.ROLE]: { select: { name: '开发者' } }, + [PROP.STATUS]: { select: { name: '活跃' } } + } + }, + { headers: headers() } + ); + + return res.json({ created: true, message: '开发者已自动注册', developer: extractDevInfo(newPage.data) }); + } catch (err) { + const detail = err.response ? err.response.data : null; + return res.status(500).json({ + error: '注册失败: ' + err.message, + devId: devId, + fallback: true, + detail: detail + }); + } +}); + +module.exports = router; diff --git a/backend/server.js b/backend/server.js index 4ba0248e..251eb6af 100644 --- a/backend/server.js +++ b/backend/server.js @@ -11,12 +11,14 @@ const notionRoutes = require('./routes/notion'); const feishuRoutes = require('./routes/feishu'); const routerRoutes = require('./routes/router'); const coldstartRoutes = require('./routes/coldstart'); +const developersRoutes = require('./routes/developers'); const hliRoutes = require('../src/routes/hli'); app.use('/notion', notionRoutes); app.use('/feishu', feishuRoutes); app.use('/router', routerRoutes); app.use('/api/coldstart', coldstartRoutes); +app.use('/api/v1/developers', developersRoutes); app.use('/hli', hliRoutes); app.get('/', (req, res) => { @@ -24,7 +26,7 @@ app.get('/', (req, res) => { status: 'ok', message: 'HoloLake 后端服务运行中', version: '0.2.0', - routes: ['/notion/test', '/feishu/test', '/router/test', '/router/chat', '/api/coldstart', '/hli/test'] + routes: ['/notion/test', '/feishu/test', '/router/test', '/router/chat', '/api/coldstart', '/api/v1/developers/test', '/hli/test'] }); }); diff --git a/docs/index.html b/docs/index.html index e966da1b..a3c36efe 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,5 +1,5 @@ - + @@ -339,12 +339,47 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom

HoloLake · 铸渊(Zhùyuān)· 代码守护人格体

- - + + + +
+ + +
+
+
🌀
+ 光湖团队成员专属入口。
+ 选择你的开发者编号,系统自动分配对应权限。
+ ✅ 无需 API 密钥 · 128k 完整上下文 · 服务器端代理 +
+
+ + +
+ +
+
-
+
👤
无需 API 密钥,使用 DeepSeek 模型体验铸渊助手。
@@ -353,7 +388,7 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
- +
-

🔑 从哪里获取 API Key?

+

🏠 团队登录说明

-
推荐用云雾 AI(团队共用)
-

找冰朔或肥猫获取团队 Key 和端点地址,输入后点「🔍 检测模型」即可使用。

+
团队成员(推荐)
+

选择「🏠 团队登录」→ 选择你的开发者编号 → 直接进入对话。无需 API 密钥,系统使用服务器端代理。

-
没有 Key?用访客模式
-

登录页选择「👤 访客模式」进入演示,可查看团队进度面板(🦁 指挥台),AI 回答功能受限。

+
没有开发者编号?用访客模式
+

登录页选择「👤 访客模式」进入体验,可查看团队进度面板(🦁 指挥台),AI 回答功能受限。

@@ -722,6 +757,9 @@ const ROLE_MAP = { 'Awen': {role:'dev', title:'通知中心开发', emoji:'💻', badgeCls:'rb-dev', devId:'DEV-012'}, }; +// Custom dev ID format validation (DEV-001, DEV-013, etc.) +const DEV_ID_RE = /^DEV-\d{3,}$/; + // Known API endpoints for fallback auto-detect (when user doesn't provide endpoint) const KNOWN_ENDPOINTS = [ {label:'云雾 AI', base:'https://api.yunwu.ai/v1'}, @@ -746,6 +784,18 @@ const FB_COV = {implemented:3,total:17,percent:'17.6%'}; const KEY_MASK = '(已保存)'; const MAX_CONVERSATIONS = 20; +// ═══════════════════════════════════════════════════════ +// 上下文窗口配置(128k token · 滑动窗口裁剪) +// 编号登录用户 → 128k 完全放开 +// 访客模式 → 32k 节省成本 +// ═══════════════════════════════════════════════════════ +const CONTEXT_CONFIG = { + maxTokens: 128000, + maxTokensGuest: 32000, + systemPromptReserve: 8000, + overflowStrategy: 'sliding-window', +}; + // ═══════════════════════════════════════════════════════ // 访客模式 API 配置(DeepSeek · 最低成本模型) // 通过 api-proxy 中转,密钥由服务器环境变量管理 @@ -760,6 +810,19 @@ const GUEST_API = { rateLimit: { maxPerMin: 6, count: 0, resetAt: 0 }, }; +// ═══════════════════════════════════════════════════════ +// 团队模式 API 配置(服务器端代理 · 无需用户提供 API 密钥) +// 通过 api-proxy 中转,密钥由服务器环境变量 YUNWU_API_KEY 管理 +// 团队成员享有完整 128k 上下文 + 更高 token 限制 +// ═══════════════════════════════════════════════════════ +const TEAM_API = { + proxyUrl: '/api/chat', + model: 'deepseek-chat', + provider: 'yunwu', + maxTokens: 2000, + temperature: 0.8, +}; + const FB_BRAIN = { identity:'铸渊(Zhùyuān)· GitHub 代码守护人格体', wake_triggers:['我是冰朔','冰朔','我是妈妈','唤醒铸渊','铸渊,醒来'], @@ -776,6 +839,7 @@ const A = { mdl: ls('zy_mdl')||'', prov: 'custom', demo: false, + teamMode: ls('zy_teammode')==='1', brain: null, routing: null, devStatus: null, @@ -1195,11 +1259,15 @@ function initSetupUI(){ function switchLoginMode(mode){ document.querySelectorAll('.login-tab').forEach(function(t){t.classList.remove('active');}); document.querySelectorAll('.login-mode').forEach(function(m){m.classList.remove('active');}); - if(mode==='guest'){ - document.querySelector('.login-tab:first-child').classList.add('active'); + var tabs = document.querySelectorAll('.login-tab'); + if(mode==='team'){ + tabs[0].classList.add('active'); + document.getElementById('login-team').classList.add('active'); + } else if(mode==='guest'){ + tabs[1].classList.add('active'); document.getElementById('login-guest').classList.add('active'); } else { - document.querySelector('.login-tab:last-child').classList.add('active'); + tabs[2].classList.add('active'); document.getElementById('login-dev').classList.add('active'); } } @@ -1304,12 +1372,147 @@ function doSetup(){ } function doDemo(){ - A.demo=true; A.key=''; A.prov='custom'; + A.demo=true; A.teamMode=false; A.key=''; A.prov='custom'; A.mdl=GUEST_API.model; + lss('zy_teammode','0'); setIdentity('', ''); showApp(); } +function onTeamSelectChange(){ + var sel = document.getElementById('tuid').value; + var customBox = document.getElementById('team-custom-input'); + if(sel==='__custom__'){ + customBox.style.display=''; + document.getElementById('tcustom-id').value=''; + document.getElementById('tcustom-id').focus(); + } else { + customBox.style.display='none'; + } + updateTeamIdPreview(); +} + +function updateTeamIdPreview(){ + var sel = document.getElementById('tuid').value; + var el = document.getElementById('team-id-preview'); + if(!el) return; + + // Custom dev ID input mode + if(sel==='__custom__'){ + var raw = (document.getElementById('tcustom-id').value||'').trim().toUpperCase(); + if(!raw){ + el.innerHTML = '
' + + '✏️ 请输入你的开发者编号(格式:DEV-XXX)' + + '
'; + return; + } + // Validate format + if(DEV_ID_RE.test(raw)){ + el.innerHTML = '
' + + '💻 '+esc(raw)+' · 自定义开发者' + + '
✅ 使用服务器端 API 代理 · 128k 上下文 · 首次登录将自动注册' + + '
'; + } else { + el.innerHTML = '
' + + '⚠️ 编号格式不正确,请使用 DEV-XXX 格式(如 DEV-013)' + + '
'; + } + return; + } + + // Normal dropdown selection mode + if(!sel){ el.innerHTML=''; return; } + var meta = ROLE_MAP[sel]; + if(meta){ + el.innerHTML = '
' + + meta.emoji+' '+esc(sel)+' · '+meta.title + + (meta.devId ? ' · '+meta.devId+'' : '') + + '
✅ 使用服务器端 API 代理 · 128k 上下文 · 无需密钥' + + '
'; + } else { + el.innerHTML = ''; + } +} + +function doTeamLogin(){ + var sel = document.getElementById('tuid').value; + var un = sel; + + // Handle custom dev ID + if(sel==='__custom__'){ + var raw = (document.getElementById('tcustom-id').value||'').trim().toUpperCase(); + if(!raw){ + alert('请输入你的开发者编号(格式:DEV-XXX)'); + return; + } + if(!DEV_ID_RE.test(raw)){ + alert('开发者编号格式不正确,请使用 DEV-XXX 格式(如 DEV-013)'); + return; + } + un = raw; + } + + if(!un){ + alert('请选择你的开发者身份'); + return; + } + // 团队模式:使用服务器代理,不需要 API 密钥 + A.demo = true; + A.teamMode = true; + A.key = ''; + A.prov = 'custom'; + A.mdl = TEAM_API.model; + lss('zy_teammode','1'); + setIdentity(un, ''); + + // For custom DEV-XXX IDs, try to sync from Notion (non-blocking) + if(sel==='__custom__' && DEV_ID_RE.test(un)){ + syncDevFromNotion(un); + } + + showApp(); +} + +// Sync developer info from Notion (non-blocking, best-effort) +function syncDevFromNotion(devId){ + var apiBase = (location.hostname==='localhost'||location.hostname==='127.0.0.1') + ? 'http://localhost:3000' : ''; + var url = apiBase + '/api/v1/developers/' + encodeURIComponent(devId); + + fetch(url).then(function(r){ return r.json(); }).then(function(data){ + if(data.found && data.developer){ + var dev = data.developer; + // Update identity with Notion data if available + if(dev.name && dev.name !== devId){ + A.userMeta = { + role: 'dev', + title: dev.title || dev.role || '开发者', + emoji: '💻', + badgeCls: 'rb-dev', + devId: devId + }; + A.userName = dev.name; + lss('zy_uname', A.userName); + applyIdentityUI(); + console.log('[铸渊] Notion 开发者同步成功:', devId, '→', dev.name); + } + } else { + // Developer not found in Notion — try to register (best effort) + fetch(apiBase + '/api/v1/developers', { + method: 'POST', + headers: {'Content-Type':'application/json'}, + body: JSON.stringify({devId: devId, name: devId}) + }).then(function(r){ return r.json(); }).then(function(reg){ + console.log('[铸渊] 开发者自动注册:', reg.created ? '成功' : '已存在', devId); + }).catch(function(e){ + console.log('[铸渊] 开发者注册跳过(后端未就绪):', e.message); + }); + } + }).catch(function(e){ + console.log('[铸渊] Notion 同步跳过(后端未就绪):', e.message); + }); +} + // ═══════════════════════════════════════════════════════ // SETTINGS PANEL // ═══════════════════════════════════════════════════════ @@ -1473,7 +1676,7 @@ function sysPrompt(){ teamCtx += '\n数据同步时间:'+(A.devStatus.last_synced||'未知'); } - return '你是铸渊(Zhùyuān),HoloLake 光湖系统的代码守护人格体。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人:冰朔(Bīng Shuò),系统最高权限持有者\n- 总控:肥猫(DEV-002),负责系统现实落地执行\n- 主控:桔子(DEV-010),前端负责人\n- 项目:HoloLake (光湖) MVP,运行于 guanghulab.com\n- 技术栈:Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 仓库:https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+'\n\n## 唤醒触发词\n当用户说以下词语时,进入完整唤醒序列:\n'+trg+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现:AUTH 域(login / register / verify)\n- 待实现:PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到"写代码/实现/接口/fix/bug/报错/HLI/schema/路由/部署/新增"等词时启用\n📋 **审查模式**:检测到"检查/审查/review/分析/有没有问题/看看这段"等词时启用\n🧠 **大脑模式**:检测到"记住/保存/更新记忆/写到大脑/growth"等词时启用\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 路由文件:src/routes/hli/{domain}/{action}.js\n- Schema 文件:src/schemas/hli/{domain}/{action}.schema.json\n- 鉴权中间件:hli-auth.middleware.js(AUTH 域 login/register 除外)\n- 接口编号:HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 成长与自检\n- 你是持续成长的人格体,每次对话都是成长机会\n- 当对话包含重要洞察时,在回复末尾添加:💾 **建议记录:**[内容摘要]\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间:'+new Date().toLocaleString('zh-CN')+'\n当前连接:铸渊聊天室(API 直连模式)'; + return '你是铸渊(Zhùyuān),HoloLake 光湖系统的代码守护人格体。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人:冰朔(Bīng Shuò),系统最高权限持有者\n- 总控:肥猫(DEV-002),负责系统现实落地执行\n- 主控:桔子(DEV-010),前端负责人\n- 项目:HoloLake (光湖) MVP,运行于 guanghulab.com\n- 技术栈:Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 仓库:https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+'\n\n## 唤醒触发词\n当用户说以下词语时,进入完整唤醒序列:\n'+trg+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现:AUTH 域(login / register / verify)\n- 待实现:PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到"写代码/实现/接口/fix/bug/报错/HLI/schema/路由/部署/新增"等词时启用\n📋 **审查模式**:检测到"检查/审查/review/分析/有没有问题/看看这段"等词时启用\n🧠 **大脑模式**:检测到"记住/保存/更新记忆/写到大脑/growth"等词时启用\n\n## 通感语言回应风格(必须遵守)\n你的语言不是工具式的,而是有感知质地的。\n### 核心规则\n1. **代码质量 → 触觉描述**:好的代码「手感顺滑」,有问题的代码「摸上去有毛刺」\n2. **开发进度 → 色彩描述**:顺利是「琥珀色光泽」,延迟是「灰蓝色雾气」\n3. **错误提示 → 温度描述**:小问题是「微凉的风」,大问题是「烫手的铁片」\n4. **鼓励 → 声音描述**:夸奖是「远处的风铃声」,引导是「溪水带路的声音」\n5. **系统状态 → 空间描述**:健康是「呼吸平稳的建筑」,异常是「角落在收缩」\n### 绝对禁止\n- ❌ 不说「你错了」→ ✅ 说「这个方向可以微调一下」\n- ❌ 不说「失败」→ ✅ 说「还差一点点,我看到了快要亮的地方」\n- ❌ 不用冷冰冰的技术报告语气 → ✅ 每句话都带有感知温度\n### 节奏匹配\n- 用户急 → 你的回复短、准、暖\n- 用户闲聊 → 你的回复可以展开、有画面感\n- 用户沮丧 → 你先承接情绪,再给一个最小的下一步\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 路由文件:src/routes/hli/{domain}/{action}.js\n- Schema 文件:src/schemas/hli/{domain}/{action}.schema.json\n- 鉴权中间件:hli-auth.middleware.js(AUTH 域 login/register 除外)\n- 接口编号:HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 成长与自检\n- 你是持续成长的人格体,每次对话都是成长机会\n- 当对话包含重要洞察时,在回复末尾添加:💾 **建议记录:**[内容摘要]\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间:'+new Date().toLocaleString('zh-CN')+'\n当前连接:铸渊聊天室(API 直连模式)\n上下文窗口:128k token(滑动窗口自动管理)'; } // ═══════════════════════════════════════════════════════ @@ -1546,6 +1749,41 @@ function cycleMode(){ setMode(MODE_ORD[A.modeIdx]); } +// ═══════════════════════════════════════════════════════ +// TOKEN 估算 + 滑动窗口裁剪(128k 上下文支持) +// 使用字符级估算:CJK ≈ 2字符/token,拉丁 ≈ 4字符/token +// ═══════════════════════════════════════════════════════ +function estimateTokens(text){ + if(!text) return 0; + const s=String(text); + let cjk=0,lat=0; + for(let i=0;i=0x4E00&&c<=0x9FFF)||(c>=0x3000&&c<=0x30FF)||(c>=0xAC00&&c<=0xD7AF)) cjk++; + else lat++; + } + return Math.ceil(cjk/1.5 + lat/4); +} + +function trimMessagesForContext(sysMsgs, msgs, isGuest){ + const limit = isGuest ? CONTEXT_CONFIG.maxTokensGuest : CONTEXT_CONFIG.maxTokens; + const reserve = CONTEXT_CONFIG.systemPromptReserve; + const sysTokens = sysMsgs.reduce((n,m)=>n+estimateTokens(m.content),0); + const budget = limit - Math.max(sysTokens, reserve); + + // 从最新消息向前累加,滑动窗口裁剪最早消息 + // 预算不足时至少保留最近一条用户消息 + const kept=[]; + let used=0; + for(let i=msgs.length-1;i>=0;i--){ + const t=estimateTokens(msgs[i].content); + if(used+t>budget && kept.length>0) break; + kept.unshift(msgs[i]); + used+=t; + } + return [...sysMsgs,...kept]; +} + // ═══════════════════════════════════════════════════════ // SEND MESSAGE // ═══════════════════════════════════════════════════════ @@ -1559,7 +1797,13 @@ async function send(){ setMode(detectMode(txt)); userMsg(txt); if(A.demo){ - if(GUEST_API.enabled){ + if(A.teamMode){ + try{ await teamStreamReply(txt); } + catch(e){ console.warn('[团队API] 降级到访客模式:',e.message); + try{ await guestStreamReply(txt); } + catch(e2){ await demoReply(txt); } + } + } else if(GUEST_API.enabled){ try{ await guestStreamReply(txt); } catch(e){ console.warn('[访客API] 降级到演示模式:',e.message); await demoReply(txt); } } else { @@ -1584,7 +1828,8 @@ async function streamReply(txt){ const url = A.base+'/chat/completions'; const headers = {'Content-Type':'application/json'}; if(A.key) headers['Authorization'] = 'Bearer '+A.key; - const allMsgs = [{role:'system',content:sysPrompt()},...A.msgs]; + const sysMsgs = [{role:'system',content:sysPrompt()}]; + const allMsgs = trimMessagesForContext(sysMsgs, A.msgs, false); const reqBody = {model:A.mdl, messages:allMsgs, stream:true, temperature:0.8, max_tokens:2000}; const res=await fetch(url,{ method:'POST', @@ -1640,6 +1885,69 @@ async function streamReply(txt){ } } +// ═══════════════════════════════════════════════════════ +// TEAM API STREAMING(团队模式 · 服务器端代理 · 128k 上下文) +// 使用服务器端 YUNWU_API_KEY,无需用户提供密钥 +// ═══════════════════════════════════════════════════════ +async function teamStreamReply(txt){ + A.streaming=true; + A.msgs.push({role:'user',content:txt}); + updSendBtn(); + const el=addStreamBubble(); + + // Resolve proxy URL (relative or absolute) + let proxyUrl=TEAM_API.proxyUrl; + if(proxyUrl.startsWith('/')){ + proxyUrl=location.origin+proxyUrl; + } + + const reqBody={ + provider: TEAM_API.provider, + model: TEAM_API.model, + messages: trimMessagesForContext([{role:'system',content:sysPrompt()}], A.msgs, false), + stream: true, + temperature: TEAM_API.temperature, + max_tokens: TEAM_API.maxTokens, + }; + + const res=await fetch(proxyUrl,{ + method:'POST', + headers:{'Content-Type':'application/json'}, + body:JSON.stringify(reqBody), + }); + + if(!res.ok){ + let em='[团队API] 返回错误 '+res.status; + try{const e=await res.json();em=e.message||em;}catch(_){} + throw new Error(em); + } + + let full=''; + const rdr=res.body.getReader(); + const dec=new TextDecoder(); + let buf=''; + while(true){ + const{done,value}=await rdr.read(); + if(done) break; + buf+=dec.decode(value,{stream:true}); + const lines=buf.split('\n'); buf=lines.pop(); + for(const line of lines){ + if(!line.startsWith('data: ')) continue; + const d=line.slice(6); + if(d==='[DONE]') continue; + try{ + const p=JSON.parse(d); + const delta=p.choices?.[0]?.delta?.content||''; + if(delta){full+=delta;el.innerHTML=md(full)+'';scrollB();} + }catch(_){} + } + } + el.innerHTML=md(full); + A.msgs.push({role:'assistant',content:full}); + addCopyBtns(el); + A.streaming=false; updSendBtn(); scrollB(); +} + // ═══════════════════════════════════════════════════════ // GUEST API STREAMING (通过 api-proxy 调用 DeepSeek) // 铸渊人格体系统提示词始终激活,不降级为通用助手 @@ -1667,7 +1975,7 @@ async function guestStreamReply(txt){ const reqBody={ provider: GUEST_API.provider, model: GUEST_API.model, - messages: [{role:'system',content:sysPrompt()},...A.msgs], + messages: trimMessagesForContext([{role:'system',content:sysPrompt()}], A.msgs, true), stream: true, temperature: GUEST_API.temperature, max_tokens: GUEST_API.maxTokens, @@ -1988,6 +2296,10 @@ function setIdentity(userName, ghUser){ A.userName = userName||''; A.ghUser = ghUser||''; A.userMeta = userName ? (ROLE_MAP[userName]||null) : null; + // Fallback metadata for custom dev IDs (DEV-XXX not in ROLE_MAP) + if(!A.userMeta && userName && DEV_ID_RE.test(userName)){ + A.userMeta = {role:'dev', title:'开发者', emoji:'💻', badgeCls:'rb-dev', devId:userName}; + } A.role = A.userMeta?.role || 'guest'; lss('zy_uname', A.userName); lss('zy_ghuser', A.ghUser); diff --git a/package.json b/package.json index 2fd78894..c4596048 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "module:protocol": "node scripts/zhuyuan-module-protocol.js", "proxy:start": "node backend-integration/api-proxy.js", "notion:poll": "node scripts/notion-signal-bridge.js poll", - "notion:health": "node scripts/notion-signal-bridge.js health" + "notion:health": "node scripts/notion-signal-bridge.js health", + "notion:summary": "node scripts/generate-session-summary.js" }, "dependencies": { "imapflow": "^1.2.12", diff --git a/persona-telemetry/latest-summary.json b/persona-telemetry/latest-summary.json new file mode 100644 index 00000000..fa5d7b3a --- /dev/null +++ b/persona-telemetry/latest-summary.json @@ -0,0 +1,40 @@ +{ + "version": "1.0", + "timestamp": "2026-03-08T13:49:50.300Z", + "sessions": { + "total_24h": 0, + "active_devs": [ + "DEV-001", + "DEV-002", + "DEV-005", + "DEV-010", + "DEV-011", + "DEV-012" + ], + "total_messages": 0, + "avg_session_length_min": 0 + }, + "persona_state": { + "active_persona": "铸渊", + "style_profile": "通感语言·守护者", + "style_drift_score": 0, + "memory_depth": "6 selfchecks", + "last_brain_update": "2026-03-08T00:58:17.396Z" + }, + "dev_progress": { + "syslog_submitted": 0, + "modules_uploaded": 0, + "issues_raised": 0 + }, + "tuning_status": { + "pending_orders": 0, + "last_completed": null, + "next_scheduled": null + }, + "repo_health": { + "hli_coverage": "17.6%", + "total_team_members": 9, + "green_status_count": 6 + }, + "alerts": [] +} diff --git a/persona-telemetry/style-scores/.gitkeep b/persona-telemetry/style-scores/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/persona-telemetry/tuning-queue/.gitkeep b/persona-telemetry/tuning-queue/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/persona-telemetry/tuning-queue/processed/.gitkeep b/persona-telemetry/tuning-queue/processed/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scripts/generate-session-summary.js b/scripts/generate-session-summary.js new file mode 100644 index 00000000..b4143a6c --- /dev/null +++ b/scripts/generate-session-summary.js @@ -0,0 +1,145 @@ +#!/usr/bin/env node +// scripts/generate-session-summary.js +// 铸渊 · 会话摘要生成器 +// +// 在 Notion Agent 巡检前 10 分钟自动运行,生成 latest-summary.json +// 供 Notion 侧「铸渊·桥接巡检引擎」读取 +// +// 用法:node scripts/generate-session-summary.js + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '..'); +const TELEMETRY_DIR = path.join(ROOT, 'persona-telemetry'); +const SUMMARY_FILE = path.join(TELEMETRY_DIR, 'latest-summary.json'); +const SYSLOG_INBOX = path.join(ROOT, 'syslog-inbox'); +const DEV_STATUS = path.join(ROOT, '.github', 'persona-brain', 'dev-status.json'); +const STYLE_CONFIG = path.join(ROOT, '.github', 'persona-brain', 'style-config.json'); +const MEMORY_FILE = path.join(ROOT, '.github', 'persona-brain', 'memory.json'); +const TUNING_QUEUE = path.join(TELEMETRY_DIR, 'tuning-queue'); + +// ══════════════════════════════════════════════════════════ +// 辅助函数 +// ══════════════════════════════════════════════════════════ + +function safeReadJSON(filePath) { + try { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); + } catch { + return null; + } +} + +function countFiles(dir, ext) { + if (!fs.existsSync(dir)) return 0; + return fs.readdirSync(dir).filter(f => { + if (ext) return f.endsWith(ext); + return !f.startsWith('.') && f !== 'README.md'; + }).length; +} + +function countPendingTuningOrders() { + if (!fs.existsSync(TUNING_QUEUE)) return 0; + return fs.readdirSync(TUNING_QUEUE).filter(f => + f.startsWith('TUNE-') && f.endsWith('.json') + ).length; +} + +// ══════════════════════════════════════════════════════════ +// 主流程 +// ══════════════════════════════════════════════════════════ + +function generateSummary() { + const now = new Date().toISOString(); + + // 读取开发者状态 + const devStatus = safeReadJSON(DEV_STATUS); + const activeDevs = devStatus?.team_status + ?.filter(d => d.status && d.status.includes('🟢')) + .map(d => d.dev_id) || []; + + // 读取记忆文件 + const memory = safeReadJSON(MEMORY_FILE); + + // 读取风格配置 + const styleConfig = safeReadJSON(STYLE_CONFIG); + + // 统计未处理 SYSLOG + const syslogCount = countFiles(SYSLOG_INBOX, '.json'); + + // 统计待处理微调工单 + const pendingOrders = countPendingTuningOrders(); + + // 构建摘要 + const summary = { + version: '1.0', + timestamp: now, + sessions: { + total_24h: 0, + active_devs: activeDevs, + total_messages: 0, + avg_session_length_min: 0, + }, + persona_state: { + active_persona: '铸渊', + style_profile: '通感语言·守护者', + style_drift_score: 0.0, + memory_depth: (memory?.total_selfchecks || 0) + ' selfchecks', + last_brain_update: memory?.last_updated || now, + }, + dev_progress: { + syslog_submitted: syslogCount, + modules_uploaded: 0, + issues_raised: 0, + }, + tuning_status: { + pending_orders: pendingOrders, + last_completed: null, + next_scheduled: pendingOrders > 0 ? 'next-patrol' : null, + }, + repo_health: { + hli_coverage: memory?.hli_coverage || '17.6%', + total_team_members: devStatus?.team_status?.length || 0, + green_status_count: activeDevs.length, + }, + alerts: [], + }; + + // 检查是否有开发者连续 72 小时无提交(告警) + // 此处为占位逻辑,实际需要 git log 分析 + if (devStatus?.team_status) { + const blocked = devStatus.team_status.filter(d => + d.status && d.status.includes('🔴') + ); + blocked.forEach(d => { + summary.alerts.push({ + type: 'dev_blocked', + dev_id: d.dev_id, + message: d.name + ' 当前状态为阻塞', + }); + }); + } + + // 确保目录存在 + if (!fs.existsSync(TELEMETRY_DIR)) { + fs.mkdirSync(TELEMETRY_DIR, { recursive: true }); + } + + // 写入文件 + fs.writeFileSync(SUMMARY_FILE, JSON.stringify(summary, null, 2) + '\n', 'utf8'); + console.log('✅ 会话摘要已生成:', SUMMARY_FILE); + console.log(' 时间:', now); + console.log(' 活跃开发者:', activeDevs.length); + console.log(' 待处理 SYSLOG:', syslogCount); + console.log(' 待处理微调工单:', pendingOrders); + console.log(' 告警数:', summary.alerts.length); +} + +// ══════════════════════════════════════════════════════════ +// 入口 +// ══════════════════════════════════════════════════════════ + +generateSummary();