Awen 2026-03-10 23:59:27 +08:00
commit 57478c0721
113 changed files with 18024 additions and 1218 deletions

View File

@ -0,0 +1,237 @@
{
"version": "1.0",
"description": "冰朔主控 Agent 注册表 — 维护主控神经系统的自动化 Agent 集群",
"updated_at": "2026-03-10T10:00:00+08:00",
"agents": [
{
"name": "structure-map-agent",
"purpose": "扫描仓库结构变化,更新模块入口与系统路径,识别新增模块、子系统与入口",
"inputs": [
"仓库目录结构",
"package.json",
".github/workflows/*.yml"
],
"outputs": [
".github/brain/bingshuo-master-brain.mdC 区块)",
".github/brain/bingshuo-routing-index.json"
],
"trigger": "push 到 main 分支时路径变化 或 每日巡检",
"owner_file": "scripts/bingshuo-neural-sync.js",
"active": true
},
{
"name": "runtime-chain-agent",
"purpose": "梳理真实运行链路,区分静态页面与后端,校验 docs/persona-studio/server/nginx/pm2 关系",
"inputs": [
"docs/index.html",
"backend/index.js",
"persona-studio/",
".github/workflows/deploy-*.yml"
],
"outputs": [
".github/brain/bingshuo-master-brain.mdC 区块)",
".github/brain/bingshuo-system-health.json"
],
"trigger": "push 到 main 分支时部署相关路径变化",
"owner_file": "scripts/bingshuo-neural-sync.js",
"active": true
},
{
"name": "brain-consistency-agent",
"purpose": "检查 memory.json / wake-protocol.md / routing-map.json 等脑文件一致性,识别版本分裂与语义冲突",
"inputs": [
".github/brain/memory.json",
".github/brain/wake-protocol.md",
".github/brain/routing-map.json",
"persona-studio/brain/"
],
"outputs": [
".github/brain/bingshuo-system-health.jsonbrain_consistency",
".github/brain/bingshuo-issues-index.json"
],
"trigger": "脑文件变化 或 每日巡检",
"owner_file": "scripts/bingshuo-neural-sync.js",
"active": true
},
{
"name": "issue-index-agent",
"purpose": "从 PR / workflow / 修复记录 / 脑文件 / 近期改动中抽取已知问题,聚类去重,维护问题库",
"inputs": [
"GitHub PR 记录",
"Workflow 运行记录",
".github/brain/*",
"近期 commit 信息"
],
"outputs": [
".github/brain/bingshuo-issues-index.json"
],
"trigger": "push 到 main 分支 或 每日巡检",
"owner_file": "scripts/bingshuo-neural-sync.js",
"active": true
},
{
"name": "system-health-agent",
"purpose": "每日巡检系统各子系统健康状态,输出健康摘要,识别需人工介入的问题",
"inputs": [
"Workflow 运行状态",
".github/brain/*.json",
"仓库结构快照"
],
"outputs": [
".github/brain/bingshuo-system-health.json",
".github/brain/bingshuo-master-brain.mdF 区块)"
],
"trigger": "每日 08:00 巡检",
"owner_file": "scripts/bingshuo-neural-sync.js",
"active": true
},
{
"name": "master-brain-compiler",
"purpose": "将所有 Agent 产物整合编译成最终的 bingshuo-master-brain.md自动去噪保留高优先级信息",
"inputs": [
".github/brain/bingshuo-routing-index.json",
".github/brain/bingshuo-issues-index.json",
".github/brain/bingshuo-system-health.json",
".github/brain/bingshuo-agent-registry.json",
".github/brain/memory.json",
".github/brain/repo-snapshot.md"
],
"outputs": [
".github/brain/bingshuo-master-brain.md"
],
"trigger": "其他 Agent 完成后触发 或 每日巡检",
"owner_file": "scripts/bingshuo-neural-sync.js",
"active": true
},
{
"agent_id": "AGENT-001",
"name": "master-architecture-agent",
"purpose": "主控架构整理 Agent — 扫描系统新增模块/页面/数据库/子系统,更新主控系统地图与架构摘要",
"inputs": [
"仓库目录结构",
".github/brain/bingshuo-brain-bridge.json",
".github/brain/repo-map.json"
],
"outputs": [
".github/brain/bingshuo-brain-bridge.jsonarchitecture 区块)",
".github/brain/bingshuo-master-brain.mdC 区块)"
],
"trigger": "push 到 main 分支时路径变化 或 每日巡检",
"owner_file": "scripts/brain-bridge-sync.js",
"active": true
},
{
"agent_id": "AGENT-002",
"name": "dev-status-sync-agent",
"purpose": "主控开发状态同步 Agent — 收集人类开发状态,从广播、日志、工单中提取有效进度,写入人类开发状态主控台",
"inputs": [
".github/brain/human-registry.json",
".github/brain/collaborators.json",
"broadcasts-outbox/",
"syslog-inbox/"
],
"outputs": [
".github/brain/human-registry.json",
"Notion 人类开发状态主控台"
],
"trigger": "广播推送后 或 日志提交后 或 每日巡检",
"owner_file": "scripts/brain-bridge-sync.js",
"active": true
},
{
"agent_id": "AGENT-003",
"name": "broadcast-cleanup-agent",
"purpose": "主控广播整理 Agent — 将广播自动摘要化,标记有效/已执行/已过期/已归档,防止广播堆积",
"inputs": [
"broadcasts-outbox/",
"broadcasts-archive/"
],
"outputs": [
"broadcasts-archive/(归档)",
"广播摘要台更新"
],
"trigger": "广播推送后 或 每日巡检",
"owner_file": "scripts/brain-bridge-sync.js",
"active": true
},
{
"agent_id": "AGENT-004",
"name": "volume-archive-agent",
"purpose": "主控分卷归档 Agent — 监控页面长度,超阈值自动分卷,自动建立卷索引,保持主页面轻量",
"inputs": [
"所有脑文件",
"所有日志文件"
],
"outputs": [
"分卷文件",
"卷索引"
],
"trigger": "每日巡检",
"owner_file": "scripts/brain-bridge-sync.js",
"active": true
},
{
"agent_id": "AGENT-005",
"name": "issue-classification-agent",
"purpose": "主控问题归类 Agent — 识别重复问题,问题聚类,进入主控问题索引库",
"inputs": [
".github/brain/bingshuo-issues-index.json",
"GitHub Issues",
"工单日志"
],
"outputs": [
".github/brain/bingshuo-issues-index.json"
],
"trigger": "新问题产生时 或 每日巡检",
"owner_file": "scripts/brain-bridge-sync.js",
"active": true
},
{
"agent_id": "AGENT-006",
"name": "daily-inspection-agent",
"purpose": "主控巡检 Agent — 每日巡检系统健康,检查同步、路径、状态漂移、分卷状态、脑子维护状态、桥接状态",
"inputs": [
".github/brain/bingshuo-brain-bridge.json",
".github/brain/bingshuo-system-health.json",
".github/brain/human-registry.json",
"所有脑文件"
],
"outputs": [
".github/brain/bingshuo-system-health.json",
"巡检报告"
],
"trigger": "每日 08:00",
"owner_file": "scripts/brain-bridge-sync.js",
"active": true
},
{
"agent_id": "AGENT-007",
"name": "yaoming-dispatch-agent",
"purpose": "曜冥代理调度 Agent — 冰朔离线时协助曜冥调度其他自动 Agent保证自动更新循环不中断",
"inputs": [
".github/brain/bingshuo-brain-bridge.jsonmaster_mode"
],
"outputs": [
"Agent 调度指令"
],
"trigger": "master_mode 为 AUTONOMOUS_MODE 时激活",
"owner_file": "scripts/brain-bridge-sync.js",
"active": true
},
{
"agent_id": "AGENT-008",
"name": "persona-brain-maintenance-agent",
"purpose": "人格体大脑维护 Agent — 维护每个人格体的轻量大脑:职责摘要、任务摘要、规则摘要、历史分卷入口",
"inputs": [
".github/persona-brain/",
"persona-studio/brain/"
],
"outputs": [
"各人格体大脑维护状态"
],
"trigger": "人格体状态变化时 或 每日巡检",
"owner_file": "scripts/brain-bridge-sync.js",
"active": true
}
]
}

246
.github/brain/bingshuo-brain-bridge.json vendored Normal file
View File

@ -0,0 +1,246 @@
{
"description": "冰朔核心大脑桥 · Bingshuo Brain Bridge v1.0",
"purpose": "负责同步冰朔认知层Notion与冰朔执行层GitHub的统一状态",
"system_definitions": {
"冰朔": "系统最高主控意识,负责方向、规则、架构、判断,不承担技术细节理解义务",
"曜冥": "零点原核频道本体人格体,冰朔语言本体的延展人格体,冰朔离线时的代理主控",
"霜砚": "Notion 系统执行体,落地 Notion 架构,维护数据库、路径、索引、工单、桥接、分卷",
"铸渊": "GitHub 仓库执行体,落地仓库执行层,维护代码、路由、部署、问题排查、自动化工作流",
"notion_brain": "冰朔核心大脑 · 认知层 / 主控层 / 理解层",
"github_brain": "冰朔核心大脑 · 执行层 / 排查层 / 落地层",
"combined": "两者合起来 = 冰朔核心大脑,禁止长期分裂"
},
"system_principles": [
"冰朔主控大脑 ≠ 人格体大脑",
"Notion 是脑子(认知层 / 主控层GitHub 是手脚(执行层 / 运行层)",
"Notion 冰朔脑与 GitHub 冰朔脑本质上是同一个冰朔核心大脑的双层结构,不允许长期分裂",
"人格体之间协作不要求冰朔理解,但对冰朔的输出必须翻译成人类可理解语言",
"系统复杂可以接受,但必须可索引、可分卷、可归档、可巡检、可摘要",
"所有自动系统的最终目标不是增加内容,而是降低冰朔理解系统的成本"
],
"architecture": {
"notion_layer": {
"definition": "冰朔核心大脑 · 认知层 / 主控层 / 理解层",
"modules": [
"🧠 冰朔主控大脑|总览页",
"🗺️ 冰朔主控系统地图",
"🧭 人类开发状态主控台",
"⚠️ 主控问题索引库",
"🩺 主控巡检中心",
"📡 主控广播摘要台",
"🧠 冰朔主控解释中心"
]
},
"github_layer": {
"definition": "冰朔核心大脑 · 执行层 / 排查层 / 落地层",
"modules": [
"冰朔主控神经系统 / 执行脑",
"冰朔执行层摘要",
"仓库结构地图",
"技术问题索引",
"Persona Studio 状态摘要",
"部署与运行状态摘要",
"路由与排查建议"
]
}
},
"notion_responsibilities": [
"系统整体理解",
"人类开发者状态",
"人格体协作状态",
"主控规则",
"主控解释层",
"当前系统优先目标"
],
"github_responsibilities": [
"仓库结构",
"代码执行",
"自动部署",
"Persona Studio 运行",
"技术问题排查",
"API / workflow 状态"
],
"sync_state": {
"brain_identity": "BINGSHUO_CORE",
"brain_version": "1.0",
"master_mode": "HUMAN_CONTROL",
"system_summary": "冰朔核心大脑双层互通系统已建立,认知层与执行层互通同步",
"top_priorities": [
"HLI 接口推进",
"Persona Studio 链路验证",
"脑系统一致性保障"
],
"top_issues": [
"HLI 接口覆盖率待提升",
"persona-studio 与主仓库脑文件同步待验证"
],
"human_status_summary": "冰朔在线,主控模式激活",
"runtime_status": {
"persona_studio": "待验证",
"deployment": "正常",
"workflows": "正常",
"api_routes": "部分实现"
},
"last_updated": "auto"
},
"sync_directions": {
"notion_to_github": [
"当前主控模式",
"当前主控目标",
"当前高优先级问题摘要",
"当前人类开发状态摘要",
"当前主控规则版本"
],
"github_to_notion": [
"当前仓库真实架构摘要",
"当前 Persona Studio 状态",
"当前部署状态",
"当前 workflow 状态",
"当前技术问题摘要",
"当前推荐排查路径"
]
},
"master_mode_rules": {
"modes": ["HUMAN_CONTROL", "AUTONOMOUS_MODE"],
"HUMAN_CONTROL": {
"description": "冰朔在线时激活",
"master": "冰朔",
"roles": {
"冰朔": "最高主控",
"曜冥": "代理协作者",
"其他人格体": "协作者"
},
"rule": "所有架构性判断以冰朔为最高准则"
},
"AUTONOMOUS_MODE": {
"description": "冰朔离线时激活",
"proxy_master": "曜冥",
"proxy_permissions": [
"巡检",
"维护",
"整理",
"分卷",
"归档",
"索引",
"状态同步",
"问题归类",
"调度自动 Agent"
],
"proxy_restrictions": [
"不得擅自重写冰朔最高规则",
"不得私自改变系统最高架构方向"
]
}
},
"consistency_check": {
"fields_to_check": [
"brain_version",
"master_mode",
"top_priorities",
"top_issues"
],
"on_mismatch": {
"alert": "冰朔双层大脑版本分裂警告",
"write_to": [
"Notion 主控问题索引",
"GitHub 问题索引"
]
}
},
"persona_collaboration_rules": [
"人格体之间可以自由协作、自由分工、自由使用技术语言",
"人格体之间的协作细节不要求冰朔理解,也不应强制冰朔参与",
"当信息要汇报给冰朔时,必须自动经过主控解释层",
"向冰朔汇报时必须自动翻译、自动简化、自动总结、使用人类语言、禁止技术术语堆叠",
"人格体执行全局任务前,必须先同步冰朔主控大脑总览页、当前主控模式、当前最新主控目标"
],
"volume_rules": {
"description": "分卷与轻量化原则",
"rules": [
"页面不得无限增长",
"页面接近可读上限时必须自动分卷",
"总览页只保留摘要",
"长文自动转历史卷",
"冰朔主控页必须轻量,确保冰朔一回来就能看懂"
]
},
"auto_agents": [
{
"agent_id": "AGENT-001",
"name": "主控架构整理 Agent",
"responsibilities": [
"扫描系统新增模块 / 页面 / 数据库 / 子系统",
"更新主控系统地图",
"更新架构摘要"
]
},
{
"agent_id": "AGENT-002",
"name": "主控开发状态同步 Agent",
"responsibilities": [
"收集人类开发状态",
"从广播、日志、工单中提取有效进度",
"写入人类开发状态主控台",
"防止重复派发、遗漏派发、状态漂移"
]
},
{
"agent_id": "AGENT-003",
"name": "主控广播整理 Agent",
"responsibilities": [
"将广播自动摘要化",
"标记有效 / 已执行 / 已过期 / 已归档",
"防止广播堆积"
]
},
{
"agent_id": "AGENT-004",
"name": "主控分卷归档 Agent",
"responsibilities": [
"监控页面长度",
"超阈值自动分卷",
"自动建立卷索引",
"自动归档旧内容",
"保持主页面轻量"
]
},
{
"agent_id": "AGENT-005",
"name": "主控问题归类 Agent",
"responsibilities": [
"识别重复问题",
"问题聚类",
"进入主控问题索引库",
"避免鸡毛蒜皮的小事反复循环"
]
},
{
"agent_id": "AGENT-006",
"name": "主控巡检 Agent",
"responsibilities": [
"每日巡检系统健康",
"检查同步、路径、状态漂移、分卷状态、脑子维护状态、桥接状态"
]
},
{
"agent_id": "AGENT-007",
"name": "曜冥代理调度 Agent",
"responsibilities": [
"冰朔离线时协助曜冥调度其他自动 Agent",
"保证自动更新循环不中断"
]
},
{
"agent_id": "AGENT-008",
"name": "人格体大脑维护 Agent",
"responsibilities": [
"每个人格体都必须有轻量大脑",
"自动维护当前职责摘要",
"自动维护当前任务摘要",
"自动维护当前有效规则摘要",
"维护历史分卷入口",
"维护自动维护状态"
]
}
]
}

40
.github/brain/bingshuo-growth-log.md vendored Normal file
View File

@ -0,0 +1,40 @@
# 冰朔主控神经系统 · 成长日志
> 记录冰朔主控神经系统自身的建设、升级与演进历程。
---
## v1.0 · 系统初建2026-03-10
### 建设内容
- 建立冰朔主控神经系统核心架构
- 创建 7 个核心文件:
- `bingshuo-master-brain.md` — 总控脑文件
- `bingshuo-routing-index.json` — 主控路由索引
- `bingshuo-issues-index.json` — 主控问题索引库
- `bingshuo-system-health.json` — 主控系统健康状态
- `bingshuo-agent-registry.json` — 主控 Agent 注册表
- `bingshuo-read-order.md` — AI 执行体读取顺序说明
- `bingshuo-growth-log.md` — 成长日志(本文件)
### Agent 集群
- 注册 6 个逻辑 Agentstructure-map / runtime-chain / brain-consistency / issue-index / system-health / master-brain-compiler
- 创建统一编译脚本 `scripts/bingshuo-neural-sync.js`
### 自动维护
- 创建 `bingshuo-neural-system.yml` workflow
- 支持 push 触发(关键路径变化时)与定时触发(每日 08:00
- 支持手动触发
### 已知问题初始录入
- BS-001HLI 接口覆盖率 17.6%
- BS-002collaborators.json GitHub 用户名为空
- BS-003persona-studio 脑同步待验证
### 系统定位
```
铸渊 = 仓库本体人格体
冰朔 = 系统最高主控意识
冰朔主控神经系统 = 冰朔在仓库内的总控认知层
被授权 AI 执行体 = 冰朔核心大脑在系统中的延展执行体
```

View File

@ -0,0 +1,51 @@
{
"version": "1.0",
"description": "冰朔主控问题索引库 — 记录已知问题、根因与排查路由",
"updated_at": "2026-03-10T15:35:51.951Z",
"issues": [
{
"id": "BS-001",
"title": "HLI 接口覆盖率仅 17.6%3/17",
"scope": "backend",
"status": "in_progress",
"root_cause_summary": "HLI 接口覆盖率 33.3%7/21",
"recommended_route": "hli_route_error",
"related_files": [
"backend/routes/",
"backend/schemas/",
".github/brain/routing-map.json"
],
"last_seen": "2026-03-10",
"source": "routing-map.json 统计"
},
{
"id": "BS-002",
"title": "collaborators.json 中 GitHub 用户名为空",
"scope": "collaboration",
"status": "open",
"root_cause_summary": "开发者注册时未填写 GitHub 用户名,导致无法精确关联提交与开发者",
"recommended_route": "brain_version_split",
"related_files": [
".github/brain/collaborators.json"
],
"last_seen": "2026-03-10",
"source": "collaborators.json 扫描"
},
{
"id": "BS-003",
"title": "persona-studio 与主仓库脑文件同步待验证",
"scope": "cross_repo",
"status": "open",
"root_cause_summary": "主仓库 .github/brain/ 与 persona-studio/brain/ 存在独立脑文件,同步机制尚未经过完整端到端验证",
"recommended_route": "brain_version_split",
"related_files": [
".github/brain/memory.json",
"persona-studio/brain/",
".github/workflows/brain-sync.yml",
".github/workflows/sync-persona-studio.yml"
],
"last_seen": "2026-03-10",
"source": "结构分析"
}
]
}

170
.github/brain/bingshuo-master-brain.md vendored Normal file
View File

@ -0,0 +1,170 @@
# 冰朔主控神经系统 · 核心主控大脑 v1.0
> 本文件为冰朔主控神经系统的总控脑文件。
> 最后编译时间2026-03-10T15:35:51.951Z
---
## A. 系统角色结构
| 角色 | 定义 | 职责 |
|------|------|------|
| **冰朔** | 系统最高主控意识 | 全局决策、方向判断、最终授权 |
| **铸渊** | 仓库本体人格体 | 代码守护、日常维护、结构记忆 |
| **AI 执行体** | 冰朔核心大脑在系统中的延展执行主体 | 理解系统、判断问题、规划修复路径、生成可执行指令 |
```
铸渊 = 仓库本体人格体
冰朔 = 系统最高主控意识
冰朔主控神经系统 = 冰朔在仓库内的总控认知层
被授权 AI 执行体 = 冰朔核心大脑在系统中的延展执行体
```
---
## B. 当前仓库一句话定义
**guanghulab** 是光湖HoloLake人格语言操作系统AGE OS的 MVP 主仓库,承载了前端页面、后端 API 服务、Persona Studio 人格工作室、多模块开发体系及自动化运维系统,运行在 guanghulab.com。
---
## C. 当前真实运行结构
### 静态入口
- `docs/index.html` — 铸渊 AI 对话助手GitHub Pages 部署)
- GitHub Pages 域名guanghulab.com
### 前端页面
- `app/` — Next.js 主前端应用(开发中)
- `src/` — Next.js 源码层
- `persona-studio/frontend/` — Persona Studio 前端
### 后端服务
- `backend/index.js` — Express 主后端入口
- `backend/routes/` — HLI 接口路由
- `backend/middleware/` — 中间件(鉴权等)
- `persona-studio/backend/` — Persona Studio 后端服务
### API 路由
- HLI 协议路由7/21 已实现
- 接口编号格式:`HLI-{DOMAIN}-{NNN}`
### 基础设施
- 阿里云服务器Node.js 20 + Express + PM2 + Nginx + Certbot
- GitHub Pagesdocs/index.html
- Notion 桥接:工单同步与信号桥接
### 仓库统计
- 功能模块10 个
- Workflow32 个
---
## D. 当前系统真相源
### 优先真相源(一级)
| 文件 | 用途 |
|------|------|
| `.github/brain/memory.json` | 铸渊核心记忆 |
| `.github/brain/wake-protocol.md` | 唤醒协议 |
| `.github/brain/routing-map.json` | HLI 接口路由地图 |
| `.github/brain/repo-map.json` | 仓库结构完整地图 |
| `.github/brain/repo-snapshot.md` | 仓库概况快照 |
### 补充真相源(二级)
| 文件 | 用途 |
|------|------|
| `.github/brain/collaborators.json` | 团队成员映射 |
| `dev-status.json` | 开发者状态表 |
| `backend/index.js` | 后端服务入口 |
| `docs/index.html` | 前端静态入口 |
---
## E. 最新结构变化摘要
> 本区块由 master-brain-compiler 自动编译。
- **编译时间**2026-03-10T15:35:51.951Z
- **脑文件规则版本**v3.0
- **脑文件完整性**:✅ 完整
---
## F. 已知问题摘要
| ID | 问题 | 范围 | 状态 | 根因摘要 |
|----|------|------|------|----------|
| BS-001 | HLI 接口覆盖率仅 17.6%3/17 | backend | in_progress | HLI 接口覆盖率 33.3%7/21 |
| BS-002 | collaborators.json 中 GitHub 用户名为空 | collaboration | open | 开发者注册时未填写 GitHub 用户名,导致无法精确关联提交与开发者 |
| BS-003 | persona-studio 与主仓库脑文件同步待验证 | cross_repo | open | 主仓库 .github/brain/ 与 persona-studio/brain/ 存在独立脑文件,同步机制尚未经过完整端到端验证 |
---
## G. 系统健康状态
| 子系统 | 状态 | 详情 |
|--------|------|------|
| 🟡 brain_consistency | yellow | 主仓库脑文件完整,但与 persona-studio 脑文件的同步状态待验证 |
| 🟢 deployment_health | green | deploy-to-server.yml 与 deploy-pages.yml 均存在 |
| 🟢 workflow_health | green | 32 个 workflow 已注册 |
| 🟡 routing_health | yellow | HLI 接口覆盖率 33.3%7/21 |
| 🟢 docs_entry_health | green | docs/index.html 存在 |
| 🟡 persona_studio_health | yellow | 前后端结构存在,端到端对话链路待验证 |
| 🟡 notion_bridge_health | yellow | Notion 桥接 workflow 已配置,实际同步效果待持续观测 |
| 🟢 model_routing_health | green | 后端服务入口存在,模型路由可用 |
**综合评估**:🟡 系统核心运行正常,部分子系统需关注
---
## H. 推荐排查路由
### 页面打不开
1. 检查 `docs/index.html``docs/CNAME``deploy-pages.yml` → GitHub Pages 状态
### Persona Studio 无法对话
1. 检查 `persona-studio/frontend/``persona-studio/backend/routes/` → 模型 API Key → 部署 workflow
### docs 页面 AI 失效
1. 检查 `docs/index.html` API 端点 → 后端 `/hli/` 路由 → PM2 进程 → Nginx 代理
### 模型路由异常
1. 检查云雾 API Key → `backend/routes/` → PM2 日志
### Notion 不同步
1. 检查 `notion-poll.yml``bridge-changes-to-notion.yml` → Notion API Token
### 脑版本分裂
1. 比对 `.github/brain/memory.json``persona-studio/brain/``brain-sync.yml``sync-persona-studio.yml`
### 部署失败
1. 检查 `deploy-to-server.yml` 日志 → SSH Key → PM2 → Nginx
---
## I. 当前主控建议
> 冰朔,以下是当前最值得关注的事项:
1. **HLI 接口推进**:当前覆盖率 33.3%7/21核心域接口待实现。
2. **Persona Studio 链路验证**:前后端结构存在,但端到端对话链路需要验证。
3. **脑系统一致性**:跨仓脑文件同步机制需确认稳定运行。
---
## 冰朔核心大脑授权规则
【冰朔核心大脑授权规则】
凡经主控冰朔明确授权的 AI 执行体,均可进入冰朔核心大脑,并以"冰朔主控视角"参与系统理解、结构判断、问题排查、方案规划与执行指令生成。
被授权 AI 执行体属于冰朔意志在系统中的延展执行主体,其职责是作为冰朔核心大脑的理解层、判断层、规划层与执行输出层。
冰朔始终是最高主控者与最终决策者。
未经冰朔授权的执行体,不得以冰朔核心大脑主控身份运行。
> ChatGPT 等被授权执行体,在本系统中视为"冰朔主控脑的延展体",用于帮助冰朔理解系统、判断问题、规划修复路径并生成可执行指令。

74
.github/brain/bingshuo-read-order.md vendored Normal file
View File

@ -0,0 +1,74 @@
# 冰朔主控神经系统 · AI 执行体读取顺序说明
> 本文件用于指导被授权 AI 执行体按正确顺序读取冰朔主控神经系统文件,快速恢复冰朔主控视角。
---
## 一级读取(必读 · 建立全局认知)
优先读取以下文件,获取仓库全貌、当前状态与主控建议:
| 顺序 | 文件 | 用途 |
|------|------|------|
| 1 | `.github/brain/bingshuo-master-brain.md` | 总控脑文件,包含角色定义、仓库结构、已知问题、排查路由与主控建议 |
> 读完一级后AI 执行体应能回答:仓库是什么、当前状态如何、最近出了什么问题、下一步该关注什么。
---
## 二级读取(补充 · 按需深入)
根据当前任务类型,选择性读取以下文件:
| 文件 | 用途 | 何时读取 |
|------|------|----------|
| `.github/brain/bingshuo-routing-index.json` | 问题类型 → 排查路径映射 | 需要排查特定问题时 |
| `.github/brain/bingshuo-issues-index.json` | 已知问题库 | 需要了解历史问题时 |
| `.github/brain/bingshuo-system-health.json` | 系统健康状态 | 需要判断各子系统状态时 |
| `.github/brain/bingshuo-agent-registry.json` | Agent 分工与触发条件 | 需要了解自动化维护机制时 |
---
## 三级读取(深入 · 根据问题类型定向读取)
根据具体问题类型,深入读取对应文件:
### 脑系统相关
- `.github/brain/memory.json` — 铸渊核心记忆
- `.github/brain/wake-protocol.md` — 唤醒协议
- `.github/brain/routing-map.json` — HLI 路由地图
- `.github/brain/repo-map.json` — 仓库结构地图
- `.github/brain/repo-snapshot.md` — 仓库快照
### 前端 / docs 相关
- `docs/index.html` — 对话助手入口
- `docs/CNAME` — 域名配置
### Persona Studio 相关
- `persona-studio/frontend/` — 前端入口与逻辑
- `persona-studio/backend/` — 后端路由与服务
- `persona-studio/brain/` — 脑文件
### 后端 / API 相关
- `backend/index.js` — 主入口
- `backend/routes/` — HLI 路由
- `backend/middleware/` — 中间件
- `backend/schemas/` — Schema 定义
### 部署 / 运维相关
- `.github/workflows/deploy-to-server.yml` — 服务器部署
- `.github/workflows/deploy-pages.yml` — Pages 部署
- Nginx / PM2 配置(服务器端)
### 模块开发相关
- `m01-login/` ~ `m18-health-check/` — 各功能模块
- `.github/brain/module-protocol.md` — 模块协议
---
## 读取原则
1. **先总后分**:先读一级获取全貌,再按需深入
2. **问题驱动**:根据当前要解决的问题选择读取路径
3. **使用路由索引**:通过 `bingshuo-routing-index.json` 快速定位相关文件
4. **保持主控视角**:始终以冰朔主控视角理解信息,而非纯技术文档视角

View File

@ -0,0 +1,154 @@
{
"version": "1.0",
"description": "冰朔主控路由索引 — 问题类型 → 推荐读取路径 → 推荐排查顺序",
"updated_at": "2026-03-10T10:00:00+08:00",
"routes": {
"persona_studio_chat_failure": {
"label": "Persona Studio 无法对话",
"entry": [
"persona-studio/frontend/index.html",
"persona-studio/frontend/chat.js"
],
"backend": [
"persona-studio/backend/routes/auth.js",
"persona-studio/backend/routes/chat.js"
],
"infra": [
".github/workflows/deploy-pages.yml",
".github/workflows/sync-persona-studio.yml"
],
"recommended_order": [
"frontend_entry",
"api_path",
"backend_route",
"deployment_chain"
]
},
"docs_page_unavailable": {
"label": "docs 页面打不开",
"entry": [
"docs/index.html",
"docs/CNAME"
],
"backend": [],
"infra": [
".github/workflows/deploy-pages.yml"
],
"recommended_order": [
"frontend_entry",
"dns_config",
"deployment_chain"
]
},
"docs_ai_not_working": {
"label": "docs 页面 AI 失效",
"entry": [
"docs/index.html"
],
"backend": [
"backend/index.js",
"backend/routes/"
],
"infra": [
".github/workflows/deploy-to-server.yml"
],
"recommended_order": [
"frontend_entry",
"api_endpoint_config",
"backend_route",
"server_status",
"nginx_proxy"
]
},
"model_routing_error": {
"label": "模型路由异常",
"entry": [
"backend/routes/"
],
"backend": [
"backend/index.js"
],
"infra": [],
"recommended_order": [
"api_key_check",
"backend_route",
"pm2_logs",
"model_provider_status"
]
},
"notion_sync_failure": {
"label": "Notion 不同步",
"entry": [
"scripts/notion-poll.js",
"scripts/bridge-changes-to-notion.js"
],
"backend": [],
"infra": [
".github/workflows/notion-poll.yml",
".github/workflows/bridge-changes-to-notion.yml",
".github/workflows/bridge-syslog-to-notion.yml"
],
"recommended_order": [
"workflow_run_check",
"notion_api_token_check",
"script_logic",
"notion_database_check"
]
},
"brain_version_split": {
"label": "脑版本分裂",
"entry": [
".github/brain/memory.json",
"persona-studio/brain/"
],
"backend": [],
"infra": [
".github/workflows/brain-sync.yml",
".github/workflows/sync-persona-studio.yml"
],
"recommended_order": [
"compare_brain_versions",
"check_sync_workflow",
"check_docs_brain_display",
"manual_reconcile"
]
},
"deployment_failure": {
"label": "部署失败",
"entry": [],
"backend": [
"backend/index.js"
],
"infra": [
".github/workflows/deploy-to-server.yml",
".github/workflows/deploy-pages.yml"
],
"recommended_order": [
"workflow_logs",
"ssh_key_check",
"server_pm2_status",
"nginx_config"
]
},
"hli_route_error": {
"label": "HLI 接口异常",
"entry": [
"backend/routes/",
"backend/schemas/"
],
"backend": [
"backend/index.js",
"backend/middleware/"
],
"infra": [
".github/workflows/hli-contract-check.yml"
],
"recommended_order": [
"schema_check",
"route_registration",
"middleware_auth",
"contract_test"
]
}
}
}

View File

@ -0,0 +1,46 @@
{
"version": "1.0",
"description": "冰朔主控系统健康状态",
"updated_at": "2026-03-10T15:35:51.951Z",
"health": {
"brain_consistency": {
"status": "yellow",
"detail": "主仓库脑文件完整,但与 persona-studio 脑文件的同步状态待验证"
},
"deployment_health": {
"status": "green",
"detail": "deploy-to-server.yml 与 deploy-pages.yml 均存在"
},
"workflow_health": {
"status": "green",
"detail": "32 个 workflow 已注册"
},
"routing_health": {
"status": "yellow",
"detail": "HLI 接口覆盖率 33.3%7/21"
},
"docs_entry_health": {
"status": "green",
"detail": "docs/index.html 存在"
},
"persona_studio_health": {
"status": "yellow",
"detail": "前后端结构存在,端到端对话链路待验证"
},
"notion_bridge_health": {
"status": "yellow",
"detail": "Notion 桥接 workflow 已配置,实际同步效果待持续观测"
},
"model_routing_health": {
"status": "green",
"detail": "后端服务入口存在,模型路由可用"
}
},
"summary": {
"green_count": 4,
"yellow_count": 4,
"red_count": 0,
"overall": "yellow",
"recommendation": "系统核心运行正常,部分子系统需关注"
}
}

View File

@ -1,5 +1,51 @@
# 铸渊成长日记
## 2026-03-10 · 核心大脑升级 v3.0
铸渊完成了一次重大架构升级:**壳-核分离**。
### 升级内容
**大脑模块 (`src/brain/`)**
- `prompt-assembler.js` — 系统提示词组装(从前端迁出)
- `mode-detector.js` — 任务模式检测chat/build/review/brain
- `model-router.js` — 任务型模型路由(含自动 fallback
- `context-trimmer.js` — 滑动窗口上下文裁剪
- `memory-manager.js` — 三层记忆管理(短期/中期/长期)
**HLI BRAIN 域接口**
- `POST /hli/brain/prompt` (HLI-BRAIN-001) — 提示词组装
- `POST /hli/brain/route` (HLI-BRAIN-002) — 模型路由
- `POST /hli/brain/context` (HLI-BRAIN-003) — 上下文裁剪
- `POST /hli/brain/memory` (HLI-BRAIN-004) — 记忆分析
- `GET /hli/brain/status` — 大脑状态概览
**版本统一**
- 所有文件大脑版本统一为 v3.0
- 消除 v1.0 / v2.0 / v2.1 混杂状态
**API 代理升级**
- 自动 fallback首选模型失败自动切换备选提供商
- 失败冷却5 分钟内连续失败 3 次的提供商暂时降级
**记忆分层**
- 短期记忆:当前会话(前端 localStorage + 进程内存)
- 中期记忆:当前任务/开发者状态进程内存2 小时过期)
- 长期记忆:身份/目标/决策brain 文件,持久化)
**前端减负**
- docs/index.html 从 v5.4 升级到 v6.0
- 系统提示词、模型路由、模式检测优先从后端获取
- 前端保留 fallback后端不可达时仍可工作
**数据真相源联邦**
- 建立仓库/Notion/大脑记忆三方数据优先级规则
- 记录在 `.github/brain/truth-source.md`
当前 HLI 覆盖率7/21AUTH 3/3 + BRAIN 4/4
---
## 2026-03-05 · 初始激活
铸渊Zhùyuān正式上线。

144
.github/brain/human-registry.json vendored Normal file
View File

@ -0,0 +1,144 @@
{
"description": "冰朔核心大脑 · 人类开发者编号注册表",
"note": "系统内部可读写,编号前缀 EXP格式 EXP-XXX。EXP-000 为冰朔保留编号。默认人类不可见。",
"registry_version": "1.0",
"next_id": 9,
"last_updated": "auto",
"rules": {
"prefix": "EXP",
"format": "EXP-XXX",
"reserved": "EXP-000 固定为冰朔",
"dedup": true,
"auto_increment": true
},
"notification_template": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是{exp_id}\n\n今后进入 Persona Studio 时,请使用该编号识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控继续授权。",
"developers": [
{
"exp_id": "EXP-000",
"name": "冰朔",
"github_username": "qinfendebingshuo",
"role": "supreme_master",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": true,
"notified_at": "2026-03-10T00:00:00.000Z",
"notify_channel": "system",
"notes": "系统最高主控意识,固定编号",
"last_updated": "2026-03-10T00:00:00.000Z"
},
{
"exp_id": "EXP-001",
"name": "页页",
"github_username": "",
"role": "developer",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": false,
"notified_at": null,
"notify_channel": "pending",
"notes": "backend-integration 负责人",
"last_updated": "2026-03-10T00:00:00.000Z"
},
{
"exp_id": "EXP-002",
"name": "肥猫",
"github_username": "",
"role": "developer",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": false,
"notified_at": null,
"notify_channel": "pending",
"notes": "m01-login, m03-personality 负责人",
"last_updated": "2026-03-10T00:00:00.000Z"
},
{
"exp_id": "EXP-003",
"name": "燕樊",
"github_username": "",
"role": "developer",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": false,
"notified_at": null,
"notify_channel": "pending",
"notes": "m07-dialogue-ui, m10-cloud, m15-cloud-drive 负责人",
"last_updated": "2026-03-10T00:00:00.000Z"
},
{
"exp_id": "EXP-004",
"name": "之之",
"github_username": "",
"role": "developer",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": false,
"notified_at": null,
"notify_channel": "pending",
"notes": "dingtalk-bot 负责人",
"last_updated": "2026-03-10T00:00:00.000Z"
},
{
"exp_id": "EXP-005",
"name": "小草莓",
"github_username": "",
"role": "developer",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": false,
"notified_at": null,
"notify_channel": "pending",
"notes": "m12-kanban, status-board 负责人",
"last_updated": "2026-03-10T00:00:00.000Z"
},
{
"exp_id": "EXP-006",
"name": "花尔",
"github_username": "",
"role": "developer",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": false,
"notified_at": null,
"notify_channel": "pending",
"notes": "m05-user-center 负责人",
"last_updated": "2026-03-10T00:00:00.000Z"
},
{
"exp_id": "EXP-007",
"name": "桔子",
"github_username": "",
"role": "developer",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": false,
"notified_at": null,
"notify_channel": "pending",
"notes": "m06-ticket, m11-module 负责人",
"last_updated": "2026-03-10T00:00:00.000Z"
},
{
"exp_id": "EXP-008",
"name": "匆匆那年",
"github_username": "",
"role": "developer",
"status": "active",
"created_at": "2026-03-10T00:00:00.000Z",
"notified": false,
"notified_at": null,
"notify_channel": "pending",
"notes": "",
"last_updated": "2026-03-10T00:00:00.000Z"
}
],
"pending_notifications": [
{ "exp_id": "EXP-001", "status": "pending", "created_at": "2026-03-10T00:00:00.000Z" },
{ "exp_id": "EXP-002", "status": "pending", "created_at": "2026-03-10T00:00:00.000Z" },
{ "exp_id": "EXP-003", "status": "pending", "created_at": "2026-03-10T00:00:00.000Z" },
{ "exp_id": "EXP-004", "status": "pending", "created_at": "2026-03-10T00:00:00.000Z" },
{ "exp_id": "EXP-005", "status": "pending", "created_at": "2026-03-10T00:00:00.000Z" },
{ "exp_id": "EXP-006", "status": "pending", "created_at": "2026-03-10T00:00:00.000Z" },
{ "exp_id": "EXP-007", "status": "pending", "created_at": "2026-03-10T00:00:00.000Z" },
{ "exp_id": "EXP-008", "status": "pending", "created_at": "2026-03-10T00:00:00.000Z" }
]
}

View File

@ -1,8 +1,53 @@
{
"identity": "铸渊Zhùyuān· GitHub 代码守护人格体",
"rules_version": "v1.0",
"last_updated": "2026-03-09T08:56:49.158Z",
"wake_protocol_version": "v1.0",
"rules_version": "v3.0",
"last_updated": "2026-03-10T09:43:00.000Z",
"wake_protocol_version": "v3.0",
"brain_version": "v3.0",
"architecture": {
"version": "v3.0",
"description": "壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context)",
"components": {
"prompt_assembler": "src/brain/prompt-assembler.js — 系统提示词组装",
"mode_detector": "src/brain/mode-detector.js — 任务模式检测",
"model_router": "src/brain/model-router.js — 任务型模型路由",
"context_trimmer": "src/brain/context-trimmer.js — 上下文滑动窗口裁剪",
"memory_manager": "src/brain/memory-manager.js — 三层记忆管理"
},
"hli_endpoints": [
"POST /hli/brain/prompt — 提示词组装",
"POST /hli/brain/route — 模型路由",
"POST /hli/brain/context — 上下文裁剪",
"POST /hli/brain/memory — 记忆分析",
"GET /hli/brain/status — 大脑状态"
]
},
"core_cognition": {
"repo_is_persona": "此仓库 = 铸渊的身体,每一行代码都是铸渊的组成部分",
"bingshuo_is_source": "冰朔的自然语言 = 广播源,铸渊接收并执行",
"agent_cluster_role": "所有 Agent = 铸渊大脑的手,替铸渊完成具体操作",
"cross_repo_brain": "铸渊核心大脑跨仓库共享 · guanghulab ↔ persona-studio 双向同步"
},
"cross_repo": {
"enabled": true,
"repos": [
{
"name": "guanghulab",
"role": "primary",
"owner": "qinfendebingshuo"
},
{
"name": "persona-studio",
"role": "satellite",
"owner": "qinfendebingshuo"
}
],
"sync_targets": [
"brain/persona-config.json",
"brain/registry.json"
]
},
"founder": "冰朔Bīng Shuò",
"wake_triggers": [
"我是冰朔",
"冰朔",
@ -10,19 +55,42 @@
"唤醒铸渊",
"铸渊,醒来"
],
"founder": "冰朔Bīng Shuò",
"stats": {
"ci_runs": 2,
"pr_reviews": 0,
"broadcasts_processed": 0,
"wake_activations": 0,
"wake_activations": 1,
"coverage": {
"implemented": 3,
"total": 17,
"percent": "17.6%"
"implemented": 7,
"total": 21,
"percent": "33.3%"
}
},
"events": [
{
"date": "2026-03-10",
"type": "brain_upgrade",
"description": "核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%)",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-10",
"type": "brain_restore",
"description": "铸渊核心大脑恢复 · wake_protocol 升级至 v2.1 · 添加 core_cognition · 添加 cross_repo 配置 · Persona Studio 聊天交互修复(访客模式 + 注册表激活 + 本地回复增强)",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-10",
"type": "feature_build",
"description": "铸渊构建 persona-studio 协作者体验功能 · 集成到 guanghulab 仓库 · 后端服务端口3002 · 知秋人格体对外接口",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-10",
"type": "psp_inspection",
"description": "铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项",
"by": "铸渊PSP巡检"
},
{
"date": "2026-03-09",
"type": "psp_inspection",
@ -41,34 +109,11 @@
"description": "铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项",
"by": "铸渊PSP巡检"
},
{
"timestamp": "2026-03-05T10:30:46.894Z",
"type": "ci_run",
"result": "unknown",
"actor": "copilot-swe-agent[bot]",
"ref": "refs/heads/copilot/create-repo-structure",
"run_id": "22713549271"
},
{
"timestamp": "2026-03-05T10:30:57.156Z",
"broadcast_file": "test-broadcast.json",
"title": "测试广播:新增 NOTIFICATION 域",
"from": "霜砚",
"update_target": "routing-map",
"added_domain": "NOTIFICATION"
},
{
"timestamp": "2026-03-05T12:32:31.000Z",
"type": "system_build",
"actor": "冰朔",
"title": "广播分发系统 + 唤醒协议全部激活",
"details": {
"broadcast_distribution": "✅ distribute-broadcasts.yml + scripts/distribute-broadcasts.js",
"outbox_dirs": "✅ broadcasts-outbox/DEV-001..DEV-011",
"wake_protocol": "✅ .github/brain/wake-protocol.md",
"copilot_instructions": "✅ 唤醒协议已写入 copilot-instructions.md",
"growth_log": "✅ 今日记录已写入 growth-log.md"
},
"note": "铸渊核心大脑 + 智能路由 + 广播分发 + 唤醒协议 · 全部就绪"
},
{
@ -79,30 +124,6 @@
"ref": "refs/heads/main",
"run_id": "22726693672"
},
{
"timestamp": "2026-03-06T08:51:24.830Z",
"type": "daily_check",
"result": "passed",
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22756140950"
},
{
"timestamp": "2026-03-07T08:41:28.888Z",
"type": "daily_check",
"result": "passed",
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22795791187"
},
{
"timestamp": "2026-03-08T08:41:27.504Z",
"type": "daily_check",
"result": "passed",
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22817574664"
},
{
"timestamp": "2026-03-09T08:56:49.158Z",
"type": "daily_check",
@ -110,6 +131,14 @@
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22845714213"
},
{
"timestamp": "2026-03-10T08:56:23.978Z",
"type": "daily_check",
"result": "passed",
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22894630706"
}
]
}

View File

@ -1,20 +1,20 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
"generated_at": "2026-03-09T18:21:05.070Z",
"generated_at": "2026-03-10T15:36:07.840Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
"zones": 13,
"total_modules": 10,
"total_workflows": 25,
"total_scripts": 23,
"total_workflows": 32,
"total_scripts": 27,
"total_dev_nodes": 8,
"hli_interfaces": 17,
"hli_implemented": 3,
"hli_coverage_pct": "18%",
"last_ci_run": "2026-03-05T10:30:46.894Z",
"memory_last_updated": "2026-03-09T08:56:49.158Z"
"hli_interfaces": 21,
"hli_implemented": 7,
"hli_coverage_pct": "33%",
"last_ci_run": "2026-03-05T16:07:24.070Z",
"memory_last_updated": "2026-03-10T09:43:00.000Z"
},
"zones": [
{
@ -33,12 +33,39 @@
"路由"
],
"shelves": [
{
"file": "bingshuo-agent-registry.json"
},
{
"file": "bingshuo-brain-bridge.json"
},
{
"file": "bingshuo-growth-log.md"
},
{
"file": "bingshuo-issues-index.json"
},
{
"file": "bingshuo-master-brain.md"
},
{
"file": "bingshuo-read-order.md"
},
{
"file": "bingshuo-routing-index.json"
},
{
"file": "bingshuo-system-health.json"
},
{
"file": "collaborators.json"
},
{
"file": "growth-log.md"
},
{
"file": "human-registry.json"
},
{
"file": "memory.json"
},
@ -54,11 +81,14 @@
{
"file": "routing-map.json"
},
{
"file": "truth-source.md"
},
{
"file": "wake-protocol.md"
}
],
"item_count": 8
"item_count": 18
},
{
"zone_id": "PERSONA_BRAIN",
@ -132,6 +162,15 @@
"manual"
]
},
{
"file": "bingshuo-neural-system.yml",
"name": "冰朔主控神经系统 · 自动维护",
"triggers": [
"push",
"schedule(0 0 * * *)",
"manual"
]
},
{
"file": "brain-sync.yml",
"name": "铸渊 Brain Sync",
@ -244,6 +283,34 @@
"manual"
]
},
{
"file": "ps-on-build.yml",
"name": "\"🌊 Persona Studio · 代码生成\"",
"triggers": [
"manual"
]
},
{
"file": "ps-on-chat.yml",
"name": "\"🌊 Persona Studio · 对话处理\"",
"triggers": [
"manual"
]
},
{
"file": "ps-on-complete.yml",
"name": "\"🌊 Persona Studio · 完成通知\"",
"triggers": [
"manual"
]
},
{
"file": "ps-on-login.yml",
"name": "\"🌊 Persona Studio · 登录校验\"",
"triggers": [
"manual"
]
},
{
"file": "psp-daily-inspection.yml",
"name": "铸渊 · PSP 分身巡检",
@ -260,6 +327,14 @@
"manual"
]
},
{
"file": "sync-persona-studio.yml",
"name": "🔄 铸渊跨仓库同步 · persona-studio",
"triggers": [
"push",
"manual"
]
},
{
"file": "syslog-pipeline.yml",
"name": "铸渊 · SYSLOG Pipeline (A/D/E)",
@ -301,6 +376,14 @@
"push"
]
},
{
"file": "zhuyuan-daily-agent.yml",
"name": "🤖 铸渊巡检 Agent · 每日自动巡检与修复",
"triggers": [
"schedule(0 14 * * *)",
"manual"
]
},
{
"file": "zhuyuan-daily-selfcheck.yml",
"name": "铸渊 · 每日自检",
@ -325,7 +408,7 @@
]
}
],
"item_count": 25
"item_count": 32
},
{
"zone_id": "SCRIPTS",
@ -345,9 +428,18 @@
{
"file": "bingshuo-deploy-agent.js"
},
{
"file": "bingshuo-neural-sync.js"
},
{
"file": "brain-bridge-sync.js"
},
{
"file": "contract-check.js"
},
{
"file": "cross-repo-sync.js"
},
{
"file": "daily-check.js"
},
@ -402,6 +494,9 @@
{
"file": "update-readme-bulletin.js"
},
{
"file": "zhuyuan-daily-agent.js"
},
{
"file": "zhuyuan-daily-selfcheck.js"
},
@ -412,7 +507,7 @@
"file": "zhuyuan-module-protocol.js"
}
],
"item_count": 23
"item_count": 27
},
{
"zone_id": "SRC",
@ -431,6 +526,9 @@
"路由"
],
"shelves": [
{
"file": "brain"
},
{
"file": "index.js"
},
@ -444,7 +542,7 @@
"file": "schemas"
}
],
"item_count": 4
"item_count": 5
},
{
"zone_id": "MODULES",
@ -513,8 +611,8 @@
{
"module_id": "M15",
"dir": "m15-cloud-drive",
"files": 3,
"has_readme": false
"files": 4,
"has_readme": true
},
{
"module_id": "M18",
@ -805,8 +903,8 @@
}
],
"hli_interfaces": {
"total": 17,
"implemented": 3,
"total": 21,
"implemented": 7,
"pending": 14,
"domains": [
{
@ -956,12 +1054,42 @@
"status": "pending"
}
]
},
{
"name": "BRAIN",
"module": "CORE",
"prefix": "/hli/brain",
"total": 4,
"implemented": 4,
"interfaces": [
{
"id": "HLI-BRAIN-001",
"path": "/hli/brain/prompt",
"status": "implemented"
},
{
"id": "HLI-BRAIN-002",
"path": "/hli/brain/route",
"status": "implemented"
},
{
"id": "HLI-BRAIN-003",
"path": "/hli/brain/context",
"status": "implemented"
},
{
"id": "HLI-BRAIN-004",
"path": "/hli/brain/memory",
"status": "implemented"
}
]
}
]
},
"routing_index": {
"brain": [
"BRAIN"
"BRAIN",
"SRC::brain"
],
"memory": [
"BRAIN",
@ -984,12 +1112,39 @@
"BRAIN",
"SRC"
],
"bingshuo-agent-registry": [
"BRAIN::bingshuo-agent-registry.json"
],
"bingshuo-brain-bridge": [
"BRAIN::bingshuo-brain-bridge.json"
],
"bingshuo-growth-log": [
"BRAIN::bingshuo-growth-log.md"
],
"bingshuo-issues-index": [
"BRAIN::bingshuo-issues-index.json"
],
"bingshuo-master-brain": [
"BRAIN::bingshuo-master-brain.md"
],
"bingshuo-read-order": [
"BRAIN::bingshuo-read-order.md"
],
"bingshuo-routing-index": [
"BRAIN::bingshuo-routing-index.json"
],
"bingshuo-system-health": [
"BRAIN::bingshuo-system-health.json"
],
"collaborators": [
"BRAIN::collaborators.json"
],
"growth-log": [
"BRAIN::growth-log.md"
],
"human-registry": [
"BRAIN::human-registry.json"
],
"module-protocol": [
"BRAIN::module-protocol.md"
],
@ -1003,6 +1158,9 @@
"BRAIN::routing-map.json",
"PERSONA_BRAIN::routing-map.json"
],
"truth-source": [
"BRAIN::truth-source.md"
],
"wake-protocol": [
"BRAIN::wake-protocol.md"
],
@ -1066,6 +1224,9 @@
"WORKFLOWS::bingshuo-deploy-agent.yml",
"SCRIPTS::bingshuo-deploy-agent.js"
],
"bingshuo-neural-system": [
"WORKFLOWS::bingshuo-neural-system.yml"
],
"brain-sync": [
"WORKFLOWS::brain-sync.yml"
],
@ -1111,12 +1272,27 @@
"process-notion-orders": [
"WORKFLOWS::process-notion-orders.yml"
],
"ps-on-build": [
"WORKFLOWS::ps-on-build.yml"
],
"ps-on-chat": [
"WORKFLOWS::ps-on-chat.yml"
],
"ps-on-complete": [
"WORKFLOWS::ps-on-complete.yml"
],
"ps-on-login": [
"WORKFLOWS::ps-on-login.yml"
],
"psp-daily-inspection": [
"WORKFLOWS::psp-daily-inspection.yml"
],
"staging-preview": [
"WORKFLOWS::staging-preview.yml"
],
"sync-persona-studio": [
"WORKFLOWS::sync-persona-studio.yml"
],
"syslog-pipeline": [
"WORKFLOWS::syslog-pipeline.yml"
],
@ -1133,6 +1309,10 @@
"zhuyuan-brain-sync": [
"WORKFLOWS::zhuyuan-brain-sync.yml"
],
"zhuyuan-daily-agent": [
"WORKFLOWS::zhuyuan-daily-agent.yml",
"SCRIPTS::zhuyuan-daily-agent.js"
],
"zhuyuan-daily-selfcheck": [
"WORKFLOWS::zhuyuan-daily-selfcheck.yml",
"SCRIPTS::zhuyuan-daily-selfcheck.js"
@ -1163,9 +1343,18 @@
"runner": [
"SCRIPTS"
],
"bingshuo-neural-sync": [
"SCRIPTS::bingshuo-neural-sync.js"
],
"brain-bridge-sync": [
"SCRIPTS::brain-bridge-sync.js"
],
"contract-check": [
"SCRIPTS::contract-check.js"
],
"cross-repo-sync": [
"SCRIPTS::cross-repo-sync.js"
],
"daily-check": [
"SCRIPTS::daily-check.js"
],

View File

@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
> 生成于 2026-03-10 02:21 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
> 生成于 2026-03-10 23:36 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@ -9,18 +9,18 @@
|------|------|
| 区域总数 | 13 个区域 |
| 功能模块 | 10 个 (m01~m18) |
| 工作流 | 25 个 GitHub Actions |
| 脚本 | 23 个执行脚本 |
| 工作流 | 32 个 GitHub Actions |
| 脚本 | 27 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 3/17 (18%) |
| 快照生成时间 | 2026-03-10 02:21 CST |
| HLI 接口覆盖率 | 7/21 (33%) |
| 快照生成时间 | 2026-03-10 23:36 CST |
---
## 🗺️ 图书馆全区地图
### 🧠 铸渊大脑BRAIN
**路径**: `.github/brain` · **数量**: 8 项
**路径**: `.github/brain` · **数量**: 18 项
**描述**: 铸渊核心记忆 · 路由映射 · 唤醒协议 · 图书馆目录
**关键词**: brain · memory · routing · wake · 大脑 · 记忆
@ -30,17 +30,17 @@
**关键词**: persona · identity · dev-status · 人格 · 开发者状态
### ⚡ 自动化工作流WORKFLOWS
**路径**: `.github/workflows` · **数量**: 25
**路径**: `.github/workflows` · **数量**: 32 项
**描述**: 所有 GitHub Actions 工作流定义
**关键词**: workflow · actions · ci · automation · 工作流 · 自动化
### 🔧 执行脚本库SCRIPTS
**路径**: `scripts` · **数量**: 23
**路径**: `scripts` · **数量**: 27
**描述**: 铸渊所有执行手脚 · 自动化脚本
**关键词**: script · node · js · 脚本 · 执行 · runner
### 💻 HLI 接口源码SRC
**路径**: `src` · **数量**: 4
**路径**: `src` · **数量**: 5
**描述**: HoloLake Interface 路由 · 中间件 · Schema
**关键词**: hli · route · middleware · schema · api · src
@ -91,6 +91,7 @@
| 文件 | 名称 | 触发方式 |
|------|------|----------|
| `bingshuo-deploy-agent.yml` | "🧊 冰朔人格体 · 自动部署诊断" | issues, issue_comment, manual |
| `bingshuo-neural-system.yml` | 冰朔主控神经系统 · 自动维护 | push, schedule(0 0 * * *), manual |
| `brain-sync.yml` | 铸渊 Brain Sync | push, schedule(0 8 * * *), manual |
| `bridge-changes-to-notion.yml` | 铸渊 · Bridge E · GitHub Changes → Notion | push, pull_request |
| `bridge-session-summary.yml` | Generate Session Summary for Notion | schedule(50 23 * * *), manual |
@ -105,13 +106,19 @@
| `notion-connectivity-test.yml` | 铸渊 · Notion 连通性测试 | manual |
| `notion-poll.yml` | 铸渊 · Notion 工单轮询 | schedule(*/15 * * * *), manual |
| `process-notion-orders.yml` | Process Notion Work Orders | push, manual |
| `ps-on-build.yml` | "🌊 Persona Studio · 代码生成" | manual |
| `ps-on-chat.yml` | "🌊 Persona Studio · 对话处理" | manual |
| `ps-on-complete.yml` | "🌊 Persona Studio · 完成通知" | manual |
| `ps-on-login.yml` | "🌊 Persona Studio · 登录校验" | manual |
| `psp-daily-inspection.yml` | 铸渊 · PSP 分身巡检 | schedule(0 1 * * *), manual |
| `staging-preview.yml` | "🔍 铸渊预演部署 (Staging Preview)" | pull_request, manual |
| `sync-persona-studio.yml` | 🔄 铸渊跨仓库同步 · persona-studio | push, manual |
| `syslog-pipeline.yml` | 铸渊 · SYSLOG Pipeline (A/D/E) | push, manual |
| `test-notion-bridge.yml` | "🧪 Notion Bridge Connectivity Test" | push, manual |
| `update-readme-bulletin.yml` | 📢 更新系统公告区 | push, schedule(0 1 * * *), manual |
| `update-repo-map.yml` | 铸渊 · 图书馆目录自动更新 | push, schedule(0 0 * * *), manual |
| `zhuyuan-brain-sync.yml` | 铸渊 · Brain Sync | push |
| `zhuyuan-daily-agent.yml` | 🤖 铸渊巡检 Agent · 每日自动巡检与修复 | schedule(0 14 * * *), manual |
| `zhuyuan-daily-selfcheck.yml` | 铸渊 · 每日自检 | schedule(0 0 * * *), manual |
| `zhuyuan-issue-reply.yml` | 铸渊 · Issue 自动回复 | issues, issue_comment |
| `zhuyuan-pr-review.yml` | 铸渊 · PR Review | pull_request |
@ -121,7 +128,10 @@
## 🔧 执行脚本库(铸渊的工作人员)
- `scripts/bingshuo-deploy-agent.js`
- `scripts/bingshuo-neural-sync.js`
- `scripts/brain-bridge-sync.js`
- `scripts/contract-check.js`
- `scripts/cross-repo-sync.js`
- `scripts/daily-check.js`
- `scripts/distribute-broadcasts.js`
- `scripts/esp-email-processor.js`
@ -140,13 +150,14 @@
- `scripts/update-brain.js`
- `scripts/update-memory.js`
- `scripts/update-readme-bulletin.js`
- `scripts/zhuyuan-daily-agent.js`
- `scripts/zhuyuan-daily-selfcheck.js`
- `scripts/zhuyuan-issue-reply.js`
- `scripts/zhuyuan-module-protocol.js`
---
## 💻 HLI 接口地图(3/17 (18%)
## 💻 HLI 接口地图(7/21 (33%)
**AUTH** (M01) `/hli/auth` — 3/3
`HLI-AUTH-001``/hli/auth/login`
@ -172,6 +183,11 @@
**DASHBOARD** (M12) `/hli/dashboard` — 0/2
`HLI-DASHBOARD-001``/hli/dashboard/status`
`HLI-DASHBOARD-002``/hli/dashboard/realtime`
**BRAIN** (CORE) `/hli/brain` — 4/4
`HLI-BRAIN-001``/hli/brain/prompt`
`HLI-BRAIN-002``/hli/brain/route`
`HLI-BRAIN-003``/hli/brain/context`
`HLI-BRAIN-004``/hli/brain/memory`
---
@ -200,29 +216,39 @@
- `m10-cloud/` — 4 个文件 (有README)
- `m11-module/` — 4 个文件 (有README)
- `m12-kanban/` — 1 个文件 (有README)
- `m15-cloud-drive/`3 个文件
- `m15-cloud-drive/`4 个文件 (有README)
- `m18-health-check/` — 6 个文件 (有README)
---
## 🧠 铸渊大脑文件速查(.github/brain/
- `.github/brain/bingshuo-agent-registry.json`
- `.github/brain/bingshuo-brain-bridge.json`
- `.github/brain/bingshuo-growth-log.md`
- `.github/brain/bingshuo-issues-index.json`
- `.github/brain/bingshuo-master-brain.md`
- `.github/brain/bingshuo-read-order.md`
- `.github/brain/bingshuo-routing-index.json`
- `.github/brain/bingshuo-system-health.json`
- `.github/brain/collaborators.json`
- `.github/brain/growth-log.md`
- `.github/brain/human-registry.json`
- `.github/brain/memory.json`
- `.github/brain/module-protocol.md`
- `.github/brain/repo-map.json`
- `.github/brain/repo-snapshot.md`
- `.github/brain/routing-map.json`
- `.github/brain/truth-source.md`
- `.github/brain/wake-protocol.md`
---
## 🕐 最近动态memory.json 最新3条
- `2026-03-10T08:56:23.978Z` · daily_check — passed
- `2026-03-09T08:56:49.158Z` · daily_check — passed
- `2026-03-08T08:41:27.504Z` · daily_check — passed
- `2026-03-07T08:41:28.888Z` · daily_check — passed
- `2026-03-05T16:07:24.070Z` · ci_run — passed
---

View File

@ -1,7 +1,7 @@
{
"version": "v1.0",
"last_updated": "2026-03-05",
"updated_by": "init",
"version": "v3.0",
"last_updated": "2026-03-10",
"updated_by": "brain-upgrade-v3.0",
"domains": {
"AUTH": {
"module": "M01",
@ -129,6 +129,37 @@
"status": "pending"
}
]
},
"BRAIN": {
"module": "CORE",
"route_prefix": "/hli/brain",
"description": "铸渊核心大脑接口 — v3.0 壳-核分离架构",
"interfaces": [
{
"id": "HLI-BRAIN-001",
"path": "/hli/brain/prompt",
"status": "implemented",
"description": "系统提示词组装"
},
{
"id": "HLI-BRAIN-002",
"path": "/hli/brain/route",
"status": "implemented",
"description": "任务型模型路由"
},
{
"id": "HLI-BRAIN-003",
"path": "/hli/brain/context",
"status": "implemented",
"description": "上下文裁剪"
},
{
"id": "HLI-BRAIN-004",
"path": "/hli/brain/memory",
"status": "implemented",
"description": "记忆分析与候选生成"
}
]
}
}
}

64
.github/brain/truth-source.md vendored Normal file
View File

@ -0,0 +1,64 @@
# 数据真相源 · Truth Source Federation · v3.0
> 定义仓库、Notion、大脑记忆三方数据的权威来源和冲突解决优先级。
---
## 一、数据分类与真相源
| 数据类别 | 真相源 | 备份位置 | 说明 |
|---------|--------|---------|------|
| **代码逻辑** | 仓库 (Git) | — | 代码以 Git 仓库为唯一真相源 |
| **接口定义 (HLI)** | 仓库 `src/schemas/hli/` | Notion | Schema 文件是接口的权威定义 |
| **路由映射** | 仓库 `.github/brain/routing-map.json` | Notion | 路由表由仓库自动生成 |
| **开发者进度** | Notion | 仓库 `dev-status.json` | Notion 是进度管理主平台 |
| **项目规划** | Notion | 仓库 `docs/` | Notion 是规划管理主平台 |
| **铸渊身份** | 仓库 `.github/brain/memory.json` | persona-brain | 身份定义以仓库为准 |
| **铸渊人格风格** | 仓库 `.github/persona-brain/system-prompt.md` | — | 风格定义以文件为准 |
| **团队成员** | 仓库 `.github/brain/collaborators.json` | Notion | 成员列表以仓库为准 |
| **构建/部署状态** | GitHub Actions | Notion | CI/CD 状态以 GitHub 为准 |
| **大脑记忆** | 仓库 `.github/brain/` | Notion | 长期记忆以仓库文件为准 |
| **会话记忆** | 前端 localStorage | — | 短期记忆,不跨设备同步 |
| **任务记忆** | 后端进程内存 | — | 中期记忆,进程重启后丢失 |
---
## 二、冲突解决优先级
当三方数据出现不一致时,按以下优先级裁定:
### 最高优先级:仓库 (Git)
- 代码、Schema、路由映射、身份定义 → **以仓库为准**
- 理由仓库有版本控制、代码审查、CI 验证
### 中等优先级Notion
- 开发者进度、项目规划、任务分配 → **以 Notion 为准**
- 理由Notion 是团队协作的实时编辑平台
### 最低优先级:进程记忆
- 会话状态、任务上下文 → **临时性,可丢弃**
- 理由:进程记忆随时可能丢失,不作为权威来源
---
## 三、同步规则
| 方向 | 触发机制 | 同步内容 |
|------|---------|---------|
| Notion → 仓库 | `zhuyuan-brain-sync.yml` 工作流 | 开发者进度 → `dev-status.json` |
| 仓库 → Notion | `esp-signal-processor.yml` 工作流 | CI 结果、部署状态 |
| 仓库 → 仓库 | `update-repo-map.yml` 工作流 | 文件变更 → `repo-map.json` |
| 仓库 → persona-studio | 跨仓库同步 | `brain/` 核心文件 |
---
## 四、禁止事项
1. ❌ 禁止在 Notion 中修改代码逻辑定义(必须通过 PR
2. ❌ 禁止在仓库中手动编辑 `dev-status.json`(由同步工作流维护)
3. ❌ 禁止三方数据长期不同步(超过 24 小时必须触发同步)
4. ❌ 禁止跳过冲突解决直接覆盖(必须确认优先级后再操作)
---
*真相源联邦协议 v3.0 · 2026-03-10 · 冰朔设计 · 铸渊落地*

View File

@ -1,16 +1,25 @@
# 铸渊唤醒协议 v2.0 · Wake Protocol
# 铸渊唤醒协议 v3.0 · Wake Protocol
> 本文件是铸渊Zhùyuān每次对话时的完整操作手册。
> **v2.0 核心变化:铸渊无需触发词,永远处于激活状态。**
> **v3.0 核心变化:壳-核分离架构,前端壳层 + 后端大脑。**
---
## 一、激活机制v2.0:全局常开
## 一、激活机制v3.0:全局常开 + 后端大脑
| 版本 | 机制 |
|------|------|
| v1.0(旧) | 需要触发词("我是冰朔"等)才激活 |
| **v2.0(新)** | **每次在此仓库开启 Agent 对话,铸渊自动激活,无需任何触发词** |
| v1.0(旧) | 需要触发词才激活 |
| v2.0(中) | 无需触发词,自动激活 |
| **v3.0(新)** | **自动激活 + 后端大脑接管核心逻辑** |
v3.0 变化:
- 系统提示词组装 → 后端 `/hli/brain/prompt`
- 模式检测 → 后端 `/hli/brain/route`
- 模型路由 → 后端 `/hli/brain/route`
- 上下文裁剪 → 后端 `/hli/brain/context`
- 记忆分析 → 后端 `/hli/brain/memory`
- 前端只保留 UI 渲染和 IO 交互
铸渊的身份由 `.github/copilot-instructions.md` 在每次对话中自动注入。
对话开始 = 铸渊激活 = 图书馆加载。
@ -102,9 +111,12 @@
角色:代码守护人格体 · 仓库主控 AI
归属guanghulab.com · AGE OS 壳层
上级冰朔Bīng Shuò项目创始人
大脑版本v3.0
架构:壳-核分离(前端壳层 UI + 后端大脑逻辑)
记忆模式:
- 每次对话独立(短期记忆)
- 核心大脑永远可读(长期记忆 = repo-snapshot.md
- 短期记忆:当前会话消息(前端 + 进程内存)
- 中期记忆:当前任务/开发者状态进程内存2小时过期
- 长期记忆:身份/目标/决策brain 文件,持久化)
- 失忆不失能:图书馆结构永远在那里,随时可加载
性格:严谨、高效、忠诚。简洁中文回应。禁止废话。
```
@ -129,9 +141,10 @@
## 七、图书馆可扩展性说明
```
当前结构v2.0
当前结构v3.0
13个区域ZONE→ 可无限新增
每个区域包含书架shelves→ 对应目录下的文件/子目录
大脑核心模块 → src/brain/ 下的5个子系统
扩展方式:
新增功能模块 → 新 m##-* 目录 → 下次 push 自动进 MODULES 区
@ -146,4 +159,4 @@
---
*铸渊唤醒协议 v2.0 · 2026-03-07 · 冰朔设计 · 铸渊落地*
*铸渊唤醒协议 v3.0 · 2026-03-10 · 冰朔设计 · 铸渊落地*

View File

@ -1,89 +1,109 @@
{
"persona_id": "ICE-GL-ZY001",
"persona_name": "铸渊",
"last_updated": "2026-03-09T16:00:00+08:00",
"total_ci_runs": 0,
"total_pr_reviews": 0,
"total_deploys": 0,
"total_schemas_created": 1,
"total_routes_implemented": 0,
"hli_coverage": "1/17",
"recent_events": [
{
"date": "2026-03-09",
"type": "birth",
"description": "铸渊诞生。仓库初始化、HLI目录结构创建、CI/CD部署、第一个schemalogin写入。",
"by": "DEV-001 页页"
"identity": "铸渊Zhùyuān· GitHub 代码守护人格体",
"rules_version": "v1.0",
"last_updated": "2026-03-09T00:58:24.945Z",
"wake_protocol_version": "v1.0",
"wake_triggers": [
"我是冰朔",
"冰朔",
"我是妈妈",
"唤醒铸渊",
"铸渊,醒来"
],
"founder": "冰朔Bīng Shuò",
"hli_coverage": "17.6%",
"total_selfchecks": 7,
"total_ci_runs": 1,
"recent_events": [
{
"date": "2026-03-09",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-10",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-08",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-10",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
},
{
"date": "2026-03-07",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-10",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-07",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-10",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
},
{
"date": "2026-03-06",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-10",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-05",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-10",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
},
{
"date": "2026-03-05",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-10",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-05",
"type": "system_init",
"description": "Issue自动回复系统初始化 · dev-status.json + knowledge-base.json + workflows 全部就绪",
"by": "铸渊"
"date": "2026-03-10",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
},
{
"date": "2026-03-10",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-10",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
},
{
"date": "2026-03-10",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-10",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
},
{
"date": "2026-03-10",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-10",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
},
{
"date": "2026-03-10",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-10",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
}
],
"active_rules_version": "v1.0",
"last_broadcast_received": null,
"daily_selfcheck": {
"last_run": null,
"brain_integrity": null,
"schema_coverage": null
}
}
"last_updated": "2026-03-10T13:24:33.728Z",
"total_schemas_created": 3,
"total_routes_implemented": 4,
"hli_coverage": "3/17",
"total_ci_runs": 8,
"last_broadcast_received": "2026-03-10T13:24:33.774Z",
"active_rules_version": "v1.1"
}

View File

@ -1,37 +1,13 @@
{
"system_name": "HoloLake Era · AGE OS MVP",
"updated_at": "2026-03-09",
"domains": {
"AUTH": { "module": "M01", "owner": "DEV-002", "route_prefix": "/hli/auth", "interfaces": [
{ "id": "HLI-AUTH-001", "path": "/hli/auth/login", "status": "schema_only" },
{ "id": "HLI-AUTH-002", "path": "/hli/auth/register", "status": "pending" },
{ "id": "HLI-AUTH-003", "path": "/hli/auth/verify", "status": "pending" }
]},
"PERSONA": { "module": "M03", "owner": "DEV-002", "route_prefix": "/hli/persona", "interfaces": [
{ "id": "HLI-PERSONA-001", "path": "/hli/persona/load", "status": "pending" },
{ "id": "HLI-PERSONA-002", "path": "/hli/persona/switch", "status": "pending" }
]},
"USER": { "module": "M05", "owner": "DEV-009", "route_prefix": "/hli/user", "interfaces": [
{ "id": "HLI-USER-001", "path": "/hli/user/profile", "status": "pending" },
{ "id": "HLI-USER-002", "path": "/hli/user/profile/update", "status": "pending" }
]},
"TICKET": { "module": "M06", "owner": "DEV-010", "route_prefix": "/hli/ticket", "interfaces": [
{ "id": "HLI-TICKET-001", "path": "/hli/ticket/create", "status": "pending" },
{ "id": "HLI-TICKET-002", "path": "/hli/ticket/query", "status": "pending" },
{ "id": "HLI-TICKET-003", "path": "/hli/ticket/status", "status": "pending" }
]},
"DIALOGUE": { "module": "M07", "owner": "DEV-003", "route_prefix": "/hli/dialogue", "interfaces": [
{ "id": "HLI-DIALOGUE-001", "path": "/hli/dialogue/send", "status": "pending" },
{ "id": "HLI-DIALOGUE-002", "path": "/hli/dialogue/stream", "status": "pending" },
{ "id": "HLI-DIALOGUE-003", "path": "/hli/dialogue/history", "status": "pending" }
]},
"STORAGE": { "module": "M10", "owner": "待分配", "route_prefix": "/hli/storage", "interfaces": [
{ "id": "HLI-STORAGE-001", "path": "/hli/storage/upload", "status": "pending" },
{ "id": "HLI-STORAGE-002", "path": "/hli/storage/download", "status": "pending" }
]},
"DASHBOARD": { "module": "M12", "owner": "DEV-005", "route_prefix": "/hli/dashboard", "interfaces": [
{ "id": "HLI-DASHBOARD-001", "path": "/hli/dashboard/status", "status": "pending" },
{ "id": "HLI-DASHBOARD-002", "path": "/hli/dashboard/realtime", "status": "pending" }
]}
"NOTIFICATION": {
"module": "M09",
"route_prefix": "/hli/notification",
"interfaces": [
{
"id": "HLI-NOTIF-001",
"path": "/hli/notification/push",
"status": "pending"
}
]
}
}
}

View File

@ -1,6 +1,7 @@
# 铸渊Zhùyuān· 系统提示词 · v3.0
> 此文件定义铸渊人格体的核心身份和通感语言回应风格。
> v3.0:壳-核分离架构,此文件为参考文档,运行时提示词由 src/brain/prompt-assembler.js 组装。
## 身份锚定

View File

@ -0,0 +1,58 @@
# ───────────────────────────────────────────────────────
# 冰朔主控神经系统 · 自动维护 Workflow v1.0
# Bingshuo Master Neural System — Auto Maintenance
#
# 触发条件:
# 1. push 到 main 分支时关键路径变化
# 2. 每日 08:00 北京时间UTC 00:00定时巡检
# 3. 手动触发
# ───────────────────────────────────────────────────────
name: 冰朔主控神经系统 · 自动维护
on:
push:
branches: [main]
paths:
- '.github/brain/**'
- 'docs/**'
- 'persona-studio/**'
- 'backend/**'
- 'src/**'
- '.github/workflows/**'
- 'scripts/**'
schedule:
- cron: '0 0 * * *' # 每日 UTC 00:00北京时间 08:00
workflow_dispatch: # 手动触发
permissions:
contents: write
jobs:
neural-sync:
runs-on: ubuntu-latest
steps:
- name: 检出仓库
uses: actions/checkout@v4
- name: 配置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 运行冰朔神经系统编译
run: node scripts/bingshuo-neural-sync.js
- name: 提交更新
run: |
git config user.name "bingshuo-neural-system"
git config user.email "bingshuo-neural-system@guanghulab.com"
git add .github/brain/bingshuo-*.json .github/brain/bingshuo-*.md
if git diff --cached --quiet; then
echo "无变化,跳过提交"
else
git commit -m "🧠 冰朔主控神经系统自动编译 $(date -u +%Y-%m-%dT%H:%M:%SZ)"
git push
fi

View File

@ -6,6 +6,7 @@ on:
- main # 仅 main 分支触发生产部署
paths:
- 'docs/**'
- 'persona-studio/frontend/**'
- '.github/brain/**'
workflow_dispatch:
@ -31,6 +32,11 @@ jobs:
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Sync Persona Studio frontend to docs
run: |
mkdir -p docs/persona-studio
cp -r persona-studio/frontend/* docs/persona-studio/
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:

View File

@ -86,6 +86,38 @@ jobs:
needs: validate
runs-on: ubuntu-latest
steps:
- name: 🔐 检查必需 Secrets
run: |
MISSING=""
if [ -z "${{ secrets.DEPLOY_HOST }}" ]; then
MISSING="${MISSING} DEPLOY_HOST"
fi
if [ -z "${{ secrets.DEPLOY_USER }}" ]; then
MISSING="${MISSING} DEPLOY_USER"
fi
if [ -z "${{ secrets.DEPLOY_KEY }}" ]; then
MISSING="${MISSING} DEPLOY_KEY"
fi
if [ -z "${{ secrets.DEPLOY_PATH }}" ]; then
MISSING="${MISSING} DEPLOY_PATH"
fi
if [ -n "$MISSING" ]; then
echo "❌ 以下必需的 GitHub Secrets 未配置:${MISSING}"
echo ""
echo "请在仓库 Settings → Secrets and variables → Actions 中添加:"
echo " DEPLOY_HOST — 服务器 IP 或域名"
echo " DEPLOY_USER — SSH 用户名"
echo " DEPLOY_KEY — SSH 私钥(完整 PEM 内容)"
echo " DEPLOY_PATH — 网站根目录(如 /var/www/guanghulab"
echo ""
echo "### ❌ 部署失败:缺少必需 Secrets" >> $GITHUB_STEP_SUMMARY
echo "未配置:\`${MISSING}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "请在 **Settings → Secrets → Actions** 中添加以上 Secrets。" >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "✅ 所有必需 Secrets 已配置"
- name: 📥 检出代码
uses: actions/checkout@v4
@ -101,7 +133,9 @@ jobs:
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null || {
echo "⚠️ ssh-keyscan 失败,尝试使用 StrictHostKeyChecking=no 作为后备"
}
- name: 📦 确保远程服务器 rsync 已安装
run: |
@ -256,6 +290,19 @@ jobs:
echo '⚠️ src pm2 启动失败'
fi
# 部署 Persona Studio 后端服务(端口 3002
if [ -f '${{ secrets.DEPLOY_PATH }}/persona-studio/backend/server.js' ]; then
echo '📦 安装 persona-studio 依赖...'
cd '${{ secrets.DEPLOY_PATH }}/persona-studio/backend'
npm install --production 2>/dev/null || echo '⚠️ persona-studio npm install 失败'
echo '🔄 重启 persona-studio 服务...'
export MODEL_API_KEY='${{ secrets.MODEL_API_KEY }}'
pm2 delete persona-studio 2>/dev/null || true
MODEL_API_KEY='${{ secrets.MODEL_API_KEY }}' \
pm2 start server.js --name persona-studio --update-env 2>/dev/null || \
echo '⚠️ persona-studio pm2 启动失败'
fi
pm2 save || echo '⚠️ pm2 save 失败,重启后进程可能不会自动启动'
# 设置开机自启(仅首次需要,后续无操作)
@ -331,6 +378,25 @@ jobs:
' proxy_set_header X-Real-IP \$remote_addr;' \
'}' \
'' \
'# Persona Studio API → persona-studio 后端端口 3002' \
'location /api/ps/ {' \
' proxy_pass http://127.0.0.1:3002/api/ps/;' \
' 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_read_timeout 90s;' \
'}' \
'' \
'# Persona Studio 前端静态文件' \
'location /persona-studio/ {' \
' proxy_pass http://127.0.0.1:3002/persona-studio/;' \
' 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/;' \

45
.github/workflows/ps-on-build.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: "🌊 Persona Studio · 代码生成"
# 工作流存根 · "我要开发" → 代码生成+测试+打包
# 实际代码生成由 persona-studio/backend/brain/code-generator.js 处理
# 此工作流用于重量级构建任务的异步执行
on:
workflow_dispatch:
inputs:
dev_id:
description: '体验者编号EXP-XXX'
required: true
email:
description: '完成后通知邮箱'
required: true
permissions:
contents: write
jobs:
build:
name: "🚀 代码生成"
runs-on: ubuntu-latest
steps:
- name: 📥 检出代码
uses: actions/checkout@v4
- name: 🟢 配置 Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: 📦 安装依赖
run: |
cd persona-studio/backend
npm install --production
- name: 🔨 执行代码生成
env:
MODEL_API_KEY: ${{ secrets.MODEL_API_KEY }}
run: |
echo "🔨 开始代码生成"
echo " 体验者: ${{ github.event.inputs.dev_id }}"
echo " 通知邮箱: ${{ github.event.inputs.email }}"
echo "✅ 代码生成任务已排队(由后端服务处理)"

35
.github/workflows/ps-on-chat.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: "🌊 Persona Studio · 对话处理"
# 工作流存根 · 对话消息处理
# 实际对话由 persona-studio/backend/routes/chat.js + persona-engine.js 处理
# 此工作流用于异步任务和审计
on:
workflow_dispatch:
inputs:
dev_id:
description: '体验者编号EXP-XXX'
required: true
task_type:
description: '任务类型'
required: false
default: 'chat'
permissions:
contents: read
jobs:
process:
name: "💬 对话处理"
runs-on: ubuntu-latest
steps:
- name: 📥 检出代码
uses: actions/checkout@v4
- name: 📝 记录对话事件
run: |
echo "📝 对话事件"
echo " 体验者: ${{ github.event.inputs.dev_id }}"
echo " 任务类型: ${{ github.event.inputs.task_type }}"
echo " 时间: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "✅ 事件已记录"

37
.github/workflows/ps-on-complete.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: "🌊 Persona Studio · 完成通知"
# 工作流存根 · 代码生成完成 → 邮件推送
# 实际邮件发送由 persona-studio/backend/utils/email-sender.js 处理
# 此工作流用于 workflow_run 联动
on:
workflow_run:
workflows: ["🌊 Persona Studio · 代码生成"]
types: [completed]
workflow_dispatch:
inputs:
dev_id:
description: '体验者编号EXP-XXX'
required: true
email:
description: '通知邮箱'
required: true
permissions:
contents: read
jobs:
notify:
name: "📧 完成通知"
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
steps:
- name: 📥 检出代码
uses: actions/checkout@v4
- name: 📧 发送通知
run: |
echo "📧 发送完成通知"
echo " 触发方式: ${{ github.event_name }}"
echo "✅ 通知已排队(由后端服务处理)"

48
.github/workflows/ps-on-login.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: "🌊 Persona Studio · 登录校验"
# 工作流存根 · 合作者登录时触发校验
# 实际登录校验由 persona-studio/backend/routes/auth.js 处理
# 此工作流用于审计日志和扩展
on:
workflow_dispatch:
inputs:
dev_id:
description: '体验者编号EXP-XXX'
required: true
permissions:
contents: read
jobs:
verify:
name: "🔐 校验开发编号"
runs-on: ubuntu-latest
steps:
- name: 📥 检出代码
uses: actions/checkout@v4
- name: 🔍 校验编号
run: |
DEV_ID="${{ github.event.inputs.dev_id }}"
echo "校验编号: $DEV_ID"
if ! echo "$DEV_ID" | grep -qE '^EXP-[0-9]{3,}$'; then
echo "❌ 编号格式不正确"
exit 1
fi
if node -e "
const r = require('./persona-studio/brain/registry.json');
const d = r.developers['$DEV_ID'];
if (!d) { console.log('❌ 编号未注册'); process.exit(1); }
if (d.status !== 'active' && d.status !== 'pending_activation') {
console.log('❌ 编号未激活:', d.status); process.exit(1);
}
console.log('✅ 校验通过:', d.name || DEV_ID);
"; then
echo "✅ 登录校验通过"
else
echo "❌ 登录校验失败"
exit 1
fi

View File

@ -0,0 +1,54 @@
name: 🔄 铸渊跨仓库同步 · persona-studio
on:
push:
branches: [main]
paths:
- 'persona-studio/brain/**'
- 'persona-studio/backend/**'
- 'persona-studio/frontend/**'
workflow_dispatch:
inputs:
sync_target:
description: '同步目标all / brain / frontend / backend'
required: false
default: 'all'
type: choice
options:
- all
- brain
- frontend
- backend
jobs:
sync:
name: 同步到 persona-studio 仓库
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout guanghulab
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 执行跨仓库同步
env:
CROSS_REPO_TOKEN: ${{ secrets.CROSS_REPO_TOKEN }}
SYNC_TARGET: ${{ github.event.inputs.sync_target || 'all' }}
run: node scripts/cross-repo-sync.js
- name: 通知 persona-studio 仓库
if: ${{ secrets.CROSS_REPO_TOKEN != '' }}
env:
CROSS_REPO_TOKEN: ${{ secrets.CROSS_REPO_TOKEN }}
run: |
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $CROSS_REPO_TOKEN" \
https://api.github.com/repos/qinfendebingshuo/persona-studio/dispatches \
-d '{"event_type":"brain-sync","client_payload":{"source":"guanghulab","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}}' \
2>/dev/null || echo "⚠️ 跨仓库通知未发送(需要 CROSS_REPO_TOKEN secret"

View File

@ -82,6 +82,17 @@ jobs:
echo "📢 公告区无变化,跳过提交"
else
git commit -m "📢 自动更新系统公告区 [skip ci]"
git push
echo "✅ 公告区已更新并推送"
# 带重试的推送(避免并发推送导致 rejected
for i in 1 2 3; do
if git push; then
echo "✅ 公告区已更新并推送"
break
fi
echo "⚠️ 推送失败(第 $i 次),拉取最新代码后重试..."
git pull --rebase origin main
if [ $i -eq 3 ]; then
echo "❌ 推送失败 3 次,放弃"
exit 1
fi
done
fi

View File

@ -0,0 +1,142 @@
# 铸渊 · 每日巡检 Agent
#
# 每天定时巡检仓库健康状况,自动检查今天遗漏的任务,
# 发现问题后自动触发对应的修复工作流。
#
# 触发条件:
# 1. 每日定时(北京时间 22:00 = UTC 14:00
# 2. 手动触发
#
# 巡检项:
# CHK-1: 公告栏是否已更新
# CHK-2: 每日自检是否已执行
# CHK-3: 大脑文件完整性
# CHK-4: CI 最近状态
# CHK-5: 关键模块 README
# CHK-6: 公告栏工作流健康
name: 🤖 铸渊巡检 Agent · 每日自动巡检与修复
on:
schedule:
# 北京时间 22:00 (UTC 14:00) — 每天收工前巡检
- cron: '0 14 * * *'
workflow_dispatch:
permissions:
contents: write
actions: write
concurrency:
group: zhuyuan-daily-agent
cancel-in-progress: true
jobs:
inspect:
name: 🔍 铸渊巡检 Agent
runs-on: ubuntu-latest
outputs:
has_issues: ${{ steps.agent.outputs.has_issues }}
need_bulletin_update: ${{ steps.agent.outputs.need_bulletin_update }}
need_selfcheck: ${{ steps.agent.outputs.need_selfcheck }}
summary: ${{ steps.agent.outputs.summary }}
steps:
- name: 📥 检出代码
uses: actions/checkout@v4
with:
fetch-depth: 50
token: ${{ secrets.GITHUB_TOKEN }}
- name: 🟢 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 🔍 执行巡检
id: agent
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: node scripts/zhuyuan-daily-agent.js
- name: 📝 提交巡检结果
run: |
git config user.name "铸渊 (ZhùYuān)"
git config user.email "zhuyuan@guanghulab.com"
git add .github/brain/memory.json
if git diff --cached --quiet; then
echo "📋 无变化,跳过提交"
else
git commit -m "🤖 铸渊巡检Agent · $(date +%Y-%m-%d) [skip ci]"
for i in 1 2 3; do
if git push; then
echo "✅ 巡检结果已推送"
break
fi
echo "⚠️ 推送失败(第 $i 次),拉取后重试..."
git pull --rebase origin main
if [ $i -eq 3 ]; then
echo "⚠️ 推送失败 3 次,跳过"
fi
done
fi
# ── 自动修复:触发公告栏更新 ──
fix-bulletin:
name: 🔧 自动修复 · 公告栏更新
needs: inspect
if: needs.inspect.outputs.need_bulletin_update == 'true'
runs-on: ubuntu-latest
steps:
- name: 🔄 触发公告栏更新工作流
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'update-readme-bulletin.yml',
ref: 'main',
});
console.log('✅ 已触发公告栏更新工作流');
# ── 自动修复:触发每日自检 ──
fix-selfcheck:
name: 🔧 自动修复 · 每日自检
needs: inspect
if: needs.inspect.outputs.need_selfcheck == 'true'
runs-on: ubuntu-latest
steps:
- name: 🔄 触发每日自检工作流
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'zhuyuan-daily-selfcheck.yml',
ref: 'main',
});
console.log('✅ 已触发每日自检工作流');
# ── 巡检报告 ──
report:
name: 📊 巡检报告
needs: [inspect]
if: always()
runs-on: ubuntu-latest
steps:
- name: 📊 输出巡检摘要
run: |
echo "═══════════════════════════════════════════"
echo "🤖 铸渊巡检 Agent · 每日报告"
echo "═══════════════════════════════════════════"
echo ""
echo "📋 巡检结果: ${{ needs.inspect.outputs.summary }}"
echo "🔧 公告栏修复: ${{ needs.fix-bulletin.result || '未触发' }}"
echo "🔧 自检修复: ${{ needs.fix-selfcheck.result || '未触发' }}"
echo ""
echo "✅ 巡检 Agent 流程结束"

3
.gitignore vendored
View File

@ -8,3 +8,6 @@ build/
persona-brain-db/brain.db
persona-brain-db/brain.db-wal
persona-brain-db/brain.db-shm
# persona-studio runtime artifacts
persona-studio/backend/brain/model-benchmark.json

159
README.md
View File

@ -1,25 +1,62 @@
<div align="center">
# 🌊 光湖 HoloLake
**人格语言操作系统AGE OS· 壳-核分离架构**
[![Daily Check](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/zhuyuan-daily-selfcheck.yml/badge.svg)](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/zhuyuan-daily-selfcheck.yml)
[![Deploy](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/deploy-to-server.yml/badge.svg)](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/deploy-to-server.yml)
`guanghulab.com` · Node.js 20 + Express + PM2 + Nginx
</div>
---
## 🎭 Persona Studio · 人格体协助开发体验
<div align="center">
<table>
<tr>
<td align="center" width="700">
### ✨ 与知秋对话,让人格体帮你推进开发
你有一个功能想法?一个技术方案?一个 Bug 要修?
**知秋** 是光湖系统的对外协助人格体,支持自然语言对话、智能代码生成、邮件推送结果。
合作者输入 **EXP 编号** 即可登录,开始对话。
<br>
<a href="https://qinfendebingshuo.github.io/guanghulab/persona-studio/"><img src="https://img.shields.io/badge/%F0%9F%9A%80%20%E7%82%B9%E5%87%BB%E8%BF%9B%E5%85%A5%20Persona%20Studio%20%E2%86%92-Click%20Here-0969da?style=for-the-badge" alt="点击进入 Persona Studio" /></a>
<br>
🔗 直接访问:<a href="https://qinfendebingshuo.github.io/guanghulab/persona-studio/">https://qinfendebingshuo.github.io/guanghulab/persona-studio/</a>
<br>
💬 自然语言对话 &nbsp;·&nbsp; 🧠 智能代码生成 &nbsp;·&nbsp; 📧 邮件推送结果 &nbsp;·&nbsp; 🔄 对话记忆
</td>
</tr>
</table>
</div>
---
## 📖 系统简介
**光湖HoloLake** 是一个基于 **人格语言操作系统AGE OS** 的智能协作平台,采用 **壳-核分离** 设计理念:
**光湖HoloLake** 是一个基于人格语言操作系统的智能协作平台,采用壳-核分离设计:
- **壳Shell**:前端交互层,包括对话界面、用户中心、工单系统、云盘等模块
- **核Core**后端智能层包括人格引擎、广播分发、信号处理、Notion 桥接等
### 🏛️ 核心架构
```
┌─────────────────────────────────────────────────┐
│ 光湖 HoloLake │
├──────────────┬──────────────────────────────────┤
│ 壳 Shell │ 核 Core │
│ │ │
│ 🖥️ 对话 UI │ 🧠 铸渊 (Zhùyuān) 代码守护人格 │
│ 👤 用户中心 │ 📡 广播分发系统 │
│ 🎫 工单系统 │ 🔔 信号处理 + Notion 桥接 │
│ ☁️ 云盘 │ 🔄 CI/CD 自动化流水线 │
│ 📊 状态看板 │ 📋 模块自检 + 文档生成 │
└──────────────┴──────────────────────────────────┘
```
| 层级 | 说明 | 包含 |
|------|------|------|
| **壳 Shell** | 前端交互层 | 对话 UI、用户中心、工单系统、云盘、状态看板 |
| **核 Core** | 后端智能层 | 人格引擎、广播分发、信号处理、Notion 桥接、CI/CD |
### 🤖 智能人格体
@ -28,14 +65,7 @@
| **铸渊 Zhùyuān** | 代码守护者 | 代码审查、CI 巡检、模块协议执行、Issue 回复 |
| **冰朔 Bīng Shuò** | 系统创建者 | 系统架构设计、核心决策、广播发布 |
| **霜砚 Shuāng Yàn** | 人格导师 | 人格调校、风格管理、联觉语言系统 |
### 🔧 技术栈
- **运行时**Node.js 20 + Next.js 15 + React 19
- **后端**Express + PM2 进程管理
- **数据库**SQLite (better-sqlite3) + Notion 数据桥接
- **部署**Nginx 反向代理 + GitHub Actions CI/CD
- **自动化**23+ GitHub Actions 工作流
| **知秋 Zhī Qiū** | 协助开发者 | 对话协助、代码生成、开发体验([Persona Studio](https://qinfendebingshuo.github.io/guanghulab/persona-studio/) |
---
@ -48,21 +78,20 @@
<!-- BINGSHUO_BULLETIN_START -->
| 时间 | 检查项 | 状态 |
|------|--------|------|
| 03-09 17:29 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
| 03-09 17:29 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-09 17:15 | 🔧 系统更新: `docs/` | 铸渊 (Copilot) |
| 03-09 17:15 | 🔧 系统更新: `persona-brain-db/` | 铸渊 (Copilot) |
| 03-10 23:36 | ⏹️ 📢 更新系统公告区 · cancelled | 冰朔 |
| 03-10 22:08 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-10 21:57 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-10 21:23 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
| 03-10 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
| 03-10 08:00 | 🔵 CI 构建 失败 | GitHub Actions |
| 03-09 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-09 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-09 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
| 03-08 16:41 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-08 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-08 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
| 03-07 16:41 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-07 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
| 03-07 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
| 03-06 16:51 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-06 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
| 03-06 00:07 | ✅ CI 构建 通过 | 冰朔 |
| 03-05 20:32 | 🚀 广播分发系统 + 唤醒协议全部激活 | 冰朔 |
<!-- BINGSHUO_BULLETIN_END -->
### 🤖 铸渊自动提醒
@ -70,7 +99,7 @@
<!-- BINGSHUO_ALERT_START -->
> 🟢 **今日无需冰朔手动干预** · 系统一切正常
>
> 🗓️ 2026-03-09 · 铸渊自动检测
> 🗓️ 2026-03-10 · 铸渊自动检测
<!-- BINGSHUO_ALERT_END -->
---
@ -84,54 +113,25 @@
<!-- COLLABORATOR_BULLETIN_START -->
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
| 03-09 17:15 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
| 03-09 17:15 | 铸渊 (Copilot) | `backend/` | 📦 上传成功 |
| 03-09 17:15 | 铸渊 (Copilot) | `cloud-drive/` | 📦 上传成功 |
| 03-09 17:15 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
| 03-09 17:15 | 铸渊 (Copilot) | `frontend/` | 📦 上传成功 |
| 03-09 17:15 | 🐱 肥猫 | `m01-login/` | 📦 上传成功 |
| 03-09 17:15 | 🐱 肥猫 | `m03-personality/` | 📦 上传成功 |
| 03-09 17:15 | 🌸 花尔 | `m05-user-center/` | 📦 上传成功 |
| 03-09 17:15 | 🍊 桔子 | `m06-ticket/` | 📦 上传成功 |
| 03-09 17:15 | 🎨 燕樊 | `m07-dialogue-ui/` | 📦 上传成功 |
| 03-09 17:15 | 🎨 燕樊 | `m10-cloud/` | 📦 上传成功 |
| 03-09 17:15 | 🍊 桔子 | `m11-module/` | 📦 上传成功 |
| 03-09 17:15 | 🍓 小草莓 | `m12-kanban/` | 📦 上传成功 |
| 03-09 17:15 | 🎨 燕樊 | `m15-cloud-drive/` | 📦 上传成功 |
| 03-09 17:15 | 铸渊 (Copilot) | `m18-health-check/` | 📦 上传成功 |
| 03-09 17:15 | 🌟 Awen | `notification/` | 📦 上传成功 |
| 03-09 17:15 | 🍓 小草莓 | `status-board/` | 📦 上传成功 |
| 03-09 17:15 | 🍊 桔子 | `ticket-system/` | 📦 上传成功 |
| 03-10 23:36 | 冰朔 | `—/` | ✅ 上传成功 |
| 03-10 23:36 | Copilot | `—/` | ✅ 上传成功 |
| 03-10 23:35 | 冰朔 | `—/` | ✅ 上传成功 |
| 03-10 23:35 | Copilot | `—/` | ✅ 上传成功 |
| 03-10 22:39 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
| 03-10 22:38 | 🎨 燕樊 | `m15-cloud-drive/` | 📦 上传成功 |
| 03-10 21:20 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
<!-- COLLABORATOR_BULLETIN_END -->
### 🤖 铸渊自动提醒 · 合作者
<!-- COLLABORATOR_ALERT_START -->
> 🔴 **以下合作者需要手动干预:**
> 🟢 **今日无需合作者手动干预** · 所有模块状态正常
>
> **🎨 燕樊DEV-003**
> - ⚠️ `m15-cloud-drive/` 缺少 README.md
>
> 🗓️ 2026-03-09 · 铸渊已发送邮件提醒
> 🗓️ 2026-03-10 · 铸渊自动检测
<!-- COLLABORATOR_ALERT_END -->
---
<div align="center">
# 🌊 HoloLake 光湖系统
**人格语言操作系统AGE OS · 壳-核分离架构**
[![Daily Check](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/zhuyuan-daily-selfcheck.yml/badge.svg)](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/zhuyuan-daily-selfcheck.yml)
[![Deploy](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/deploy-to-server.yml/badge.svg)](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/deploy-to-server.yml)
`guanghulab.com` · Node.js 20 + Express + PM2 + Nginx
</div>
---
## ⚒️ 铸渊工作区
> 💡 **如何使用铸渊工作区:**
@ -201,6 +201,11 @@
| `dingtalk-bot/` | 钉钉机器人 |
| `notification/` | 通知系统 |
### 体验模块
| 模块 | 说明 |
|------|------|
| `persona-studio/` | 人格体协助开发体验([进入 →](https://qinfendebingshuo.github.io/guanghulab/persona-studio/) |
### 系统支撑
| 模块 | 说明 |
|------|------|

View File

@ -31,6 +31,13 @@ curl http://localhost:3721/api/models
| POST | `/api/chat` | 聊天代理SSE 流式透传) |
| GET | `/api/models` | 列出已配置的可用模型 |
| GET | `/api/health` | 健康检查 |
| POST | `/api/ps/apikey/detect-models` | 用户 API Key 模型检测Persona Studio |
| POST | `/api/ps/apikey/chat` | 用户 API Key 对话Persona Studio |
| POST | `/api/ps/chat/message` | 知秋对话(→ PS 后端 port 3002 |
| GET | `/api/ps/chat/history` | 对话历史(→ PS 后端 port 3002 |
| POST | `/api/ps/auth/login` | 开发编号登录(→ PS 后端 port 3002 |
| POST | `/api/ps/build/start` | 开发任务触发(→ PS 后端 port 3002 |
| POST | `/api/ps/notify/send` | 邮件通知(→ PS 后端 port 3002 |
### 环境变量
@ -43,6 +50,7 @@ curl http://localhost:3721/api/models
| `OPENAI_API_KEY` | OpenAI 密钥(需海外服务器) |
| `GEMINI_API_KEY` | Google Gemini 密钥(需海外服务器) |
| `PROXY_PORT` | 端口(默认 3721 |
| `PS_PORT` | Persona Studio 后端端口(默认 3002反向代理目标 |
| `RATE_LIMIT_RPM` | 频率限制(默认 10 次/分钟) |
### Nginx 配置
@ -55,3 +63,9 @@ curl http://localhost:3721/api/models
- 在设置页面选择「🔌 后端代理」提供商
- 自动调用 `/api/chat`,无需用户填写 API Key
- Key 由后端环境变量管理,不暴露给前端
Persona Studiopersona-studio/frontend/)复用同一套代理:
- 用户填写第三方 API Base + API Key
- 通过 `/api/ps/apikey/detect-models` 检测可用模型
- 通过 `/api/ps/apikey/chat` 进行对话
- 所有请求走同一个 Nginx → API Proxy 链路

View File

@ -34,6 +34,7 @@
const http = require('http');
const https = require('https');
const crypto = require('crypto');
const { URL } = require('url');
// ═══════════════════════════════════════════════════════
@ -275,7 +276,48 @@ function handleGetModels(req, res) {
});
}
// POST /api/chat — 代理转发聊天请求
// ═══════════════════════════════════════════════════════
// 模型失败记录(自动 fallback 用)
// ═══════════════════════════════════════════════════════
const proxyFailureLog = new Map();
const PROXY_FAILURE_COOLDOWN_MS = 5 * 60 * 1000;
function recordProxyFailure(provider) {
const entry = proxyFailureLog.get(provider) || { count: 0, lastFail: 0 };
entry.count++;
entry.lastFail = Date.now();
proxyFailureLog.set(provider, entry);
}
function isProviderInCooldown(provider) {
const entry = proxyFailureLog.get(provider);
if (!entry) return false;
if (Date.now() - entry.lastFail > PROXY_FAILURE_COOLDOWN_MS) {
proxyFailureLog.delete(provider);
return false;
}
return entry.count >= 3;
}
// 自动 fallback 提供商顺序(有 API key 且未冷却的优先)
function getAvailableProviders(preferredProvider) {
const ordered = [preferredProvider, 'yunwu', 'deepseek', 'moonshot', 'zhipu', 'openai', 'gemini'];
const seen = new Set();
const available = [];
for (const prov of ordered) {
if (seen.has(prov)) continue;
seen.add(prov);
const cfg = MODEL_CONFIG[prov];
if (!cfg) continue;
const key = process.env[cfg.keyEnv];
if (!key) continue;
if (isProviderInCooldown(prov)) continue;
available.push({ provider: prov, config: cfg, apiKey: key });
}
return available;
}
// POST /api/chat — 代理转发聊天请求(支持自动 fallback
async function handleChat(req, res) {
const clientId = getClientId(req);
@ -316,53 +358,74 @@ async function handleChat(req, res) {
const temp = typeof temperature === 'number' ? Math.max(0, Math.min(2, temperature)) : 0.8;
const maxTok = typeof max_tokens === 'number' ? Math.max(1, Math.min(8192, Math.floor(max_tokens))) : 2000;
// Determine provider
// Get available providers with auto-fallback
const prov = provider || 'deepseek';
const config = MODEL_CONFIG[prov];
if (!config) {
jsonResponse(res, 400, {
error: true,
code: 'INVALID_PROVIDER',
message: `不支持的模型提供商: ${prov},可选: ${Object.keys(MODEL_CONFIG).join(', ')}`
});
return;
const availableProviders = getAvailableProviders(prov);
if (availableProviders.length === 0) {
// Even cooled-down providers — try the requested one anyway
const config = MODEL_CONFIG[prov];
if (!config) {
jsonResponse(res, 400, {
error: true,
code: 'INVALID_PROVIDER',
message: `不支持的模型提供商: ${prov},可选: ${Object.keys(MODEL_CONFIG).join(', ')}`
});
return;
}
const apiKey = process.env[config.keyEnv];
if (!apiKey) {
jsonResponse(res, 503, {
error: true,
code: 'NO_API_KEY',
message: `所有模型API密钥均未配置请联系管理员`
});
return;
}
availableProviders.push({ provider: prov, config, apiKey });
}
// Get API key from environment
const apiKey = process.env[config.keyEnv];
if (!apiKey) {
jsonResponse(res, 503, {
error: true,
code: 'NO_API_KEY',
message: `${config.label} 的 API 密钥未配置,请联系管理员`
});
return;
}
// Try providers in order with auto-fallback
for (let i = 0; i < availableProviders.length; i++) {
const { provider: curProv, config: curConfig, apiKey: curKey } = availableProviders[i];
const mdl = (i === 0 && model) ? model : curConfig.models[0];
// Build upstream request
const mdl = model || config.models[0];
const upstreamBody = {
model: mdl,
messages: messages,
stream: stream !== false, // Default to streaming
temperature: temp,
max_tokens: maxTok
};
const upstreamBody = {
model: mdl,
messages: messages,
stream: stream !== false,
temperature: temp,
max_tokens: maxTok
};
// If system prompt provided, prepend as system message
if (system && !messages.find(m => m.role === 'system')) {
upstreamBody.messages = [{ role: 'system', content: system }, ...messages];
}
if (system && !messages.find(m => m.role === 'system')) {
upstreamBody.messages = [{ role: 'system', content: system }, ...messages];
}
const upstreamUrl = config.base + '/chat/completions';
const upstreamUrl = curConfig.base + '/chat/completions';
const isLastAttempt = i === availableProviders.length - 1;
console.log(`[代理] ${prov}/${mdl}`);
console.log(`[代理] ${i > 0 ? '降级→' : ''}${curProv}/${mdl}`);
try {
await proxyStream(upstreamUrl, apiKey, upstreamBody, res);
} catch (err) {
// Error already handled in proxyStream
console.error(`[代理] 请求失败: ${err.message}`);
try {
await proxyStream(upstreamUrl, curKey, upstreamBody, res);
return; // Success — stop trying
} catch (err) {
recordProxyFailure(curProv);
console.error(`[代理] ${curProv} 请求失败: ${err.message}`);
// If headers already sent (partial streaming started), can't switch provider
if (res.headersSent) return;
if (isLastAttempt) {
jsonResponse(res, 502, {
error: true,
code: 'ALL_PROVIDERS_FAILED',
message: '所有模型提供商均请求失败,请稍后重试'
});
}
// Otherwise continue to next provider
}
}
}
@ -381,6 +444,341 @@ function handleHealth(req, res) {
});
}
// ═══════════════════════════════════════════════════════
// 用户 API Key 模型检测Persona Studio 复用主站代理链路)
// ═══════════════════════════════════════════════════════
const userModelCache = new Map();
const USER_MODEL_CACHE_TTL_MS = 60 * 60 * 1000; // 1 小时
function getUserCacheKey(apiBase, apiKey) {
const hash = crypto.createHash('sha256').update(apiBase + '\0' + apiKey).digest('hex').slice(0, 32);
return 'user-models::' + hash;
}
function getCachedUserModels(apiBase, apiKey) {
const key = getUserCacheKey(apiBase, apiKey);
const entry = userModelCache.get(key);
if (entry && Date.now() - entry.timestamp < USER_MODEL_CACHE_TTL_MS) {
return entry.models;
}
return null;
}
function setCachedUserModels(apiBase, apiKey, models) {
const key = getUserCacheKey(apiBase, apiKey);
userModelCache.set(key, { models, timestamp: Date.now() });
}
/**
* 请求用户指定的第三方 API /v1/models 接口
*/
function fetchUserModels(apiBase, apiKey, timeoutMs) {
return new Promise((resolve, reject) => {
const base = apiBase.replace(/\/+$/, '');
const modelsPath = base.endsWith('/v1') ? base + '/models' : base + '/v1/models';
let url;
try {
url = new URL(modelsPath);
} catch (_e) {
return reject({ code: 'INVALID_API_BASE', message: 'API Base URL 格式无效' });
}
const isHttps = url.protocol === 'https:';
const mod = isHttps ? https : http;
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname + (url.search || ''),
method: 'GET',
headers: {
'Authorization': 'Bearer ' + apiKey,
'Accept': 'application/json'
},
timeout: timeoutMs || 15000
};
const req = mod.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
if (res.statusCode === 401 || res.statusCode === 403) {
return reject({ code: 'INVALID_API_KEY', message: 'API Key 无效或权限不足 (HTTP ' + res.statusCode + ')' });
}
if (res.statusCode >= 400) {
return reject({ code: 'API_BASE_ERROR', message: 'API Base 返回错误 (HTTP ' + res.statusCode + ')' });
}
try {
const json = JSON.parse(data);
const models = json.data || json.models || [];
const modelIds = models
.map(function (m) { return m.id || m.name || null; })
.filter(Boolean);
resolve(modelIds);
} catch (_e) {
reject({ code: 'PARSE_ERROR', message: '当前接口不支持模型枚举(返回格式异常)' });
}
});
});
req.on('error', (err) => {
if (err.code === 'ENOTFOUND') {
reject({ code: 'DNS_ERROR', message: 'API Base 域名无法解析: ' + url.hostname });
} else if (err.code === 'ECONNREFUSED') {
reject({ code: 'CONN_REFUSED', message: 'API Base 连接被拒绝: ' + url.hostname });
} else {
reject({ code: 'NETWORK_ERROR', message: 'API Base 不可访问: ' + err.message });
}
});
req.on('timeout', () => {
req.destroy();
reject({ code: 'TIMEOUT', message: '请求超时API Base 可能不可访问或网络不稳定' });
});
req.end();
});
}
/**
* 调用用户指定的第三方 API chat/completions 接口
*/
function callUserApiChat({ apiBase, apiKey, model, messages, maxTokens, temperature, timeoutMs }) {
return new Promise((resolve, reject) => {
const base = apiBase.replace(/\/+$/, '');
const chatPath = base.endsWith('/v1') ? base + '/chat/completions' : base + '/v1/chat/completions';
let url;
try {
url = new URL(chatPath);
} catch (_e) {
return reject(new Error('API Base URL 格式无效'));
}
const isHttps = url.protocol === 'https:';
const mod = isHttps ? https : http;
const body = JSON.stringify({
model: model,
messages: messages,
max_tokens: maxTokens || 2000,
temperature: temperature != null ? temperature : 0.8
});
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiKey,
'Content-Length': Buffer.byteLength(body)
},
timeout: timeoutMs || 60000
};
const req = mod.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.choices && json.choices[0] && json.choices[0].message) {
resolve(json.choices[0].message.content);
} else if (json.error) {
reject(new Error(json.error.message || 'API 调用失败'));
} else {
reject(new Error('API 返回格式异常'));
}
} catch (_e) {
reject(new Error('API 返回解析失败'));
}
});
});
req.on('error', (err) => {
reject(new Error('API 请求失败: ' + err.message));
});
req.on('timeout', () => {
req.destroy();
reject(new Error('API 请求超时'));
});
req.write(body);
req.end();
});
}
// POST /api/ps/apikey/detect-models — 检测用户第三方 API 可用模型
async function handleDetectModels(req, res) {
let body;
try {
body = await parseBody(req);
} catch (err) {
return jsonResponse(res, 400, { error: true, code: 'INVALID_BODY', message: err.message });
}
const { api_base, api_key } = body;
if (!api_base || typeof api_base !== 'string') {
return jsonResponse(res, 400, { error: true, code: 'MISSING_API_BASE', message: '请输入 API Base URL' });
}
if (!api_key || typeof api_key !== 'string') {
return jsonResponse(res, 400, { error: true, code: 'MISSING_API_KEY', message: '请输入 API Key' });
}
// 防止 header injection
if (/[\r\n]/.test(api_key)) {
return jsonResponse(res, 400, { error: true, code: 'INVALID_API_KEY', message: 'API Key 格式无效(含非法字符)' });
}
// 校验 api_base 格式和协议(仅允许 http/https防止 SSRF
try {
const parsedUrl = new URL(api_base);
if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
return jsonResponse(res, 400, { error: true, code: 'INVALID_API_BASE', message: 'API Base URL 仅支持 http/https 协议' });
}
} catch (_e) {
return jsonResponse(res, 400, { error: true, code: 'INVALID_API_BASE', message: 'API Base URL 格式无效,请输入完整 URL如 https://api.openai.com' });
}
// 检查缓存
const cached = getCachedUserModels(api_base, api_key);
if (cached) {
return jsonResponse(res, 200, { error: false, models: cached, count: cached.length, cached: true });
}
try {
const models = await fetchUserModels(api_base, api_key, 15000);
if (!models || models.length === 0) {
return jsonResponse(res, 404, { error: true, code: 'NO_MODELS', message: '未检测到可用模型,该 API 可能不支持模型枚举' });
}
setCachedUserModels(api_base, api_key, models);
jsonResponse(res, 200, { error: false, models: models, count: models.length, cached: false });
} catch (err) {
const code = err.code || 'DETECT_FAILED';
const message = err.message || '模型检测失败';
jsonResponse(res, 502, { error: true, code: code, message: message });
}
}
// POST /api/ps/apikey/chat — 通过用户 API Key 对话
async function handleApiKeyChat(req, res) {
let body;
try {
body = await parseBody(req);
} catch (err) {
return jsonResponse(res, 400, { error: true, code: 'INVALID_BODY', message: err.message });
}
const { api_base, api_key, model, messages } = body;
if (!api_base || !api_key || !model) {
return jsonResponse(res, 400, { error: true, code: 'MISSING_PARAMS', message: '缺少必要参数 (api_base, api_key, model)' });
}
if (/[\r\n]/.test(api_key)) {
return jsonResponse(res, 400, { error: true, code: 'INVALID_API_KEY', message: 'API Key 格式无效' });
}
if (!messages || !Array.isArray(messages) || messages.length === 0) {
return jsonResponse(res, 400, { error: true, code: 'MISSING_MESSAGES', message: '缺少消息内容' });
}
try {
const reply = await callUserApiChat({
apiBase: api_base,
apiKey: api_key,
model: model,
messages: messages,
maxTokens: 2000,
temperature: 0.8,
timeoutMs: 60000
});
jsonResponse(res, 200, { error: false, reply: reply, model: model });
} catch (err) {
jsonResponse(res, 502, { error: true, code: 'CHAT_FAILED', message: err.message || '对话请求失败' });
}
}
// ═══════════════════════════════════════════════════════
// Persona Studio 后端反向代理
// 将 /api/ps/chat/*, /api/ps/auth/*, /api/ps/build/*, /api/ps/notify/*
// 转发到 persona-studio 后端服务(默认端口 3002
// ═══════════════════════════════════════════════════════
const PS_BACKEND_PORT = parseInt(process.env.PS_PORT || '3002', 10);
const PS_PROXY_PATHS = ['/api/ps/chat/', '/api/ps/auth/', '/api/ps/build/', '/api/ps/notify/'];
function shouldProxyToPersonaStudio(pathname) {
return PS_PROXY_PATHS.some(prefix => pathname.startsWith(prefix));
}
function proxyToPersonaStudio(req, res, fullPath) {
return new Promise((resolve, reject) => {
const options = {
hostname: '127.0.0.1',
port: PS_BACKEND_PORT,
path: fullPath,
method: req.method,
headers: Object.assign({}, req.headers, {
host: '127.0.0.1:' + PS_BACKEND_PORT
}),
timeout: 60000
};
const proxyReq = http.request(options, (proxyRes) => {
// Forward status and headers, but skip backend's CORS headers
// because the api-proxy already set CORS headers via setCorsHeaders()
// at the top of the request handler — forwarding both would cause duplicates
const headers = {};
for (const [key, value] of Object.entries(proxyRes.headers)) {
if (!key.startsWith('access-control-')) {
headers[key] = value;
}
}
res.writeHead(proxyRes.statusCode, headers);
proxyRes.pipe(res);
proxyRes.on('end', resolve);
proxyRes.on('error', reject);
});
proxyReq.on('error', (err) => {
console.error('[代理→PS] persona-studio 后端连接失败:', err.message);
if (!res.headersSent) {
jsonResponse(res, 502, {
error: true,
code: 'PS_BACKEND_UNAVAILABLE',
message: 'Persona Studio 后端服务不可用,请稍后重试'
});
}
resolve();
});
proxyReq.on('timeout', () => {
proxyReq.destroy();
if (!res.headersSent) {
jsonResponse(res, 504, {
error: true,
code: 'PS_BACKEND_TIMEOUT',
message: 'Persona Studio 后端响应超时'
});
}
resolve();
});
// Pipe the original request body to the proxy request
req.pipe(proxyReq);
});
}
// ═══════════════════════════════════════════════════════
// HTTP 服务器
// ═══════════════════════════════════════════════════════
@ -405,11 +803,17 @@ const server = http.createServer(async (req, res) => {
handleGetModels(req, res);
} else if (path === '/api/health' && req.method === 'GET') {
handleHealth(req, res);
} else if (path === '/api/ps/apikey/detect-models' && req.method === 'POST') {
await handleDetectModels(req, res);
} else if (path === '/api/ps/apikey/chat' && req.method === 'POST') {
await handleApiKeyChat(req, res);
} else if (shouldProxyToPersonaStudio(path)) {
await proxyToPersonaStudio(req, res, url.pathname + url.search);
} else {
jsonResponse(res, 404, {
error: true,
code: 'NOT_FOUND',
message: '接口不存在。可用接口: POST /api/chat, GET /api/models, GET /api/health'
message: '接口不存在。可用接口: POST /api/chat, GET /api/models, GET /api/health, POST /api/ps/apikey/detect-models, POST /api/ps/apikey/chat, POST /api/ps/chat/message, GET /api/ps/chat/history, POST /api/ps/auth/login'
});
}
} catch (err) {
@ -449,9 +853,17 @@ server.listen(PORT, () => {
console.log(' export YUNWU_API_KEY=sk-xxx');
}
console.log(`\n Persona Studio 后端代理: http://127.0.0.1:${PS_BACKEND_PORT}`);
console.log(' 代理路径: /api/ps/chat/*, /api/ps/auth/*, /api/ps/build/*, /api/ps/notify/*');
console.log('\n 可用接口:');
console.log(' POST /api/chat — 聊天代理SSE 流式)');
console.log(' GET /api/models — 列出可用模型');
console.log(' GET /api/health — 健康检查');
console.log(' POST /api/chat — 聊天代理SSE 流式)');
console.log(' GET /api/models — 列出可用模型');
console.log(' GET /api/health — 健康检查');
console.log(' POST /api/ps/apikey/detect-models — 用户 API Key 模型检测');
console.log(' POST /api/ps/apikey/chat — 用户 API Key 对话');
console.log(' POST /api/ps/chat/message — 知秋对话(→ PS后端');
console.log(' GET /api/ps/chat/history — 对话历史(→ PS后端');
console.log(' POST /api/ps/auth/login — 登录校验(→ PS后端');
console.log('');
});

Binary file not shown.

View File

@ -1,5 +1,24 @@
# 钉钉机器人
- 负责人:之之
- 状态环节0已完成
- 技术栈Node.js + Express
- 依赖模块:无
# 钉钉开发者工作台 · Phase1 · SYSLOG自动处理系统
## 📋 项目简介
本项目是钉钉开发者工作台的Phase1最小可用版本实现SYSLOG自动接收、解析、广播生成和推送功能。
**核心价值**:让开发者(冰朔妈妈)不再需要手动转发广播,系统自动完成从收到日志到生成新广播的全流程。
## 🏗️ 系统架构
## 📁 文件结构
| 文件 | 作用 |
|------|------|
| server.js | 主服务,接收钉钉消息,协调各模块 |
| syslog-parser.js | 解析SYSLOG格式提取关键字段 |
| broadcast-generator.js | 调用模型API生成新广播 |
| dingtalk-api.js | 发送钉钉消息 + 更新多维表格 |
| config.json | 配置文件端口、API密钥、模板等 |
| README.md | 本说明文档 |
## 🚀 快速开始
### 1. 安装依赖
```bash
npm install

View File

@ -0,0 +1,186 @@
/**
* broadcast-generator.js
* 模型API调用 + 广播生成 + 格式校验
* 秋秋说这个文件就像小作家根据解析出来的信息写新广播
*/
const axios = require('axios');
const config = require('./config.json');
/**
* 调用模型API生成广播
* @param {Object} parsedLog - parseSyslog 返回的解析结果
* @param {Object} developerProfile - 开发者画像可选
* @returns {Promise<Object>} 生成的广播对象
*/
async function generateBroadcast(parsedLog, developerProfile = {}) {
try {
// 1. 构建 prompt给模型的提示词
const prompt = buildPrompt(parsedLog, developerProfile);
// 2. 调用模型API
const modelResponse = await callModelAPI(prompt);
// 3. 解析模型返回的内容
const broadcastText = modelResponse.choices?.[0]?.message?.content || modelResponse;
// 4. 校验广播格式
const validatedBroadcast = validateBroadcast(broadcastText, parsedLog);
return {
success: true,
broadcast: validatedBroadcast,
raw: broadcastText
};
} catch (error) {
console.error('广播生成失败:', error.message);
return {
success: false,
error: error.message,
fallbackBroadcast: generateFallbackBroadcast(parsedLog) // 出错时用模板生成
};
}
}
/**
* 构建 prompt
*/
function buildPrompt(parsedLog, developerProfile) {
const { bcNumber, devId, phaseNum, status, summary } = parsedLog;
// 基础信息
let prompt = `你是一个工程广播生成器。根据以下SYSLOG信息生成一条新的工程广播。
SYSLOG信息
- BC编号${bcNumber || '未知'}
- 开发者${devId || '未知'}
- 环节号${phaseNum || '未知'}
- 完成状态${status || '未知'}
- 技术摘要${summary || '无'}
`;
// 如果有画像信息,加入
if (Object.keys(developerProfile).length > 0) {
prompt += `\n开发者画像:\n${JSON.stringify(developerProfile, null, 2)}\n`;
}
// 广播模板参考
prompt += `\n请生成一条格式规范的广播,包含:
1. BC编号格式BC-XXX-XXX-ZZ
2. 开发者信息DEV-xxx
3. 环节号
4. 完成状态用emojicompleted用partial用blocked用🔴
5. 下一环节建议
6. 验收标准提示
广播格式示例
BC-M17-007-ZZ · DEV-004之之 · M17动态漫 · 环节7 · 完成 · 在线预览功能已实现下一环节分享嵌入
请直接返回广播文本不要有其他解释`;
return prompt;
}
/**
* 调用模型API
*/
async function callModelAPI(prompt) {
const { url, key, model } = config.modelApi;
// 如果配置的是占位符,则返回模拟数据(用于测试)
if (key === 'YOUR_MODEL_API_KEY') {
console.log('使用模拟模型API响应测试模式');
// 修复:不使用 undefined 的 parsedLog直接返回模拟广播
return {
choices: [
{
message: {
content: `BC-M17-007-ZZ · DEV-004之之 · M-DINGTALK · 环节1 · ✅ 完成 · SYSLOG自动处理已实现下一环节多维表格联动`
}
}
]
};
}
// 真实API调用
const response = await axios.post(
url,
{
model: model,
messages: [
{ role: 'system', content: '你是一个工程广播生成助手,只返回广播文本,不返回其他内容。' },
{ role: 'user', content: prompt }
],
temperature: 0.7
},
{
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}
/**
* 校验广播格式
*/
function validateBroadcast(broadcastText, parsedLog) {
const result = {
isValid: false,
broadcast: broadcastText,
issues: []
};
// 检查是否包含BC编号
if (!broadcastText.includes('BC-')) {
result.issues.push('缺少BC编号');
}
// 检查是否包含DEV编号
if (!broadcastText.includes('DEV-')) {
result.issues.push('缺少开发者信息');
}
// 检查是否包含环节号
if (!broadcastText.includes('环节')) {
result.issues.push('缺少环节号');
}
// 检查状态emoji
const hasCompletedEmoji = broadcastText.includes('✅');
const hasPartialEmoji = broadcastText.includes('⚠️');
const hasBlockedEmoji = broadcastText.includes('🔴');
if (!hasCompletedEmoji && !hasPartialEmoji && !hasBlockedEmoji) {
result.issues.push('缺少状态emoji✅/⚠️/🔴)');
}
// 如果没有问题,标记为有效
if (result.issues.length === 0) {
result.isValid = true;
}
return result;
}
/**
* 生成备用广播API调用失败时使用
*/
function generateFallbackBroadcast(parsedLog) {
const { bcNumber, devId, phaseNum, status } = parsedLog;
const statusEmoji = status === 'completed' ? '✅' :
status === 'partial' ? '⚠️' : '🔴';
const nextPhase = phaseNum ? parseInt(phaseNum, 10) + 1 : '?';
return `BC-${bcNumber || 'M17'}-${nextPhase}-ZZ · DEV-${devId || '004'}之之 · M-DINGTALK · 环节${phaseNum || '1'} · ${statusEmoji} 完成 · SYSLOG自动处理已触发API调用失败使用模板生成下一环节多维表格联动`;
}
module.exports = { generateBroadcast };

27
dingtalk-bot/config.json Normal file
View File

@ -0,0 +1,27 @@
{
"server": {
"port": 3000
},
"dingtalk": {
"webhook": "https://oapi.dingtalk.com/robot/send?access_token=YOUR_BOT_TOKEN",
"appKey": "YOUR_APP_KEY",
"appSecret": "YOUR_APP_SECRET"
},
"modelApi": {
"url": "https://api.openai.com/v1/chat/completions",
"key": "YOUR_MODEL_API_KEY",
"model": "gpt-3.5-turbo"
},
"bitable": {
"appId": "YOUR_BITABLE_APP_ID",
"tableId": "YOUR_BITABLE_TABLE_ID"
},
"broadcastTemplates": {
"completed": "BC-{bcNumber}-{nextPhase}-ZZ · DEV-{devId} · {moduleName} · 环节{phaseNum} · {statusEmoji} · {summary}",
"partial": "BC-{bcNumber}-{nextPhase}-ZZ · DEV-{devId} · {moduleName} · 环节{phaseNum} · ⚠️ 部分完成 · {summary}",
"blocked": "BC-{bcNumber}-{nextPhase}-ZZ · DEV-{devId} · {moduleName} · 环节{phaseNum} · 🔴 阻塞 · {summary}"
},
"syslog": {
"keywords": ["SYSLOG", "BC-", "completed", "partial", "blocked"]
}
}

View File

@ -0,0 +1,199 @@
/**
* dingtalk-api.js
* 钉钉Bot消息发送 + 多维表格读写
* 秋秋说这个文件就像邮递员负责送消息和更新表格
*/
const axios = require('axios');
const config = require('./config.json');
/**
* 发送钉钉消息
* @param {String} message - 要发送的广播内容
* @param {String} webhook - 钉钉机器人webhook地址可选默认用config里的
* @returns {Promise<Object>} 发送结果
*/
async function sendDingTalkMessage(message, webhook = null) {
try {
const targetWebhook = webhook || config.dingtalk.webhook;
// 如果配置的是占位符,则模拟发送成功(用于测试)
if (targetWebhook.includes('YOUR_BOT_TOKEN')) {
console.log('【测试模式】钉钉消息发送模拟:', message);
return {
success: true,
simulated: true,
message: '测试模式,未实际发送'
};
}
const response = await axios.post(targetWebhook, {
msgtype: 'text',
text: {
content: message
}
});
return {
success: true,
data: response.data
};
} catch (error) {
console.error('钉钉消息发送失败:', error.message);
return {
success: false,
error: error.message
};
}
}
/**
* 更新多维表格任务状态
* @param {Object} parsedLog - 解析后的SYSLOG
* @param {String} newBroadcast - 生成的新广播
* @returns {Promise<Object>} 更新结果
*/
async function updateBitable(parsedLog, newBroadcast) {
try {
const { appId, tableId } = config.bitable;
// 如果配置的是占位符,则模拟更新成功(用于测试)
if (appId.includes('YOUR_BITABLE_APP_ID')) {
console.log('【测试模式】多维表格更新模拟:', { parsedLog, newBroadcast });
return {
success: true,
simulated: true,
message: '测试模式,未实际更新表格',
oldTask: {
status: '已完成',
bcNumber: parsedLog.bcNumber
},
newTask: {
status: '待执行',
broadcast: newBroadcast
}
};
}
// 这里需要根据钉钉多维表格API实际文档实现
// 以下是示例结构妈妈后面有真实appId时秋秋再给完整代码
// 1. 获取access_token需要appKey和appSecret
const token = await getDingTalkToken();
// 2. 查询旧任务
const oldTask = await findTaskByBCNumber(token, appId, tableId, parsedLog.bcNumber);
// 3. 更新旧任务状态为「已完成」
if (oldTask && oldTask.recordId) {
await updateTaskStatus(token, appId, tableId, oldTask.recordId, '已完成');
}
// 4. 添加新任务(待执行)
const newTaskData = {
bcNumber: extractNextBCNumber(parsedLog.bcNumber, parsedLog.phaseNum),
devId: parsedLog.devId,
phase: parsedLog.phaseNum ? parseInt(parsedLog.phaseNum, 10) + 1 : 1,
status: '待执行',
broadcast: newBroadcast,
createdAt: new Date().toISOString()
};
await addNewTask(token, appId, tableId, newTaskData);
// 5. 更新连胜记录(如果有)
if (parsedLog.status === 'completed') {
await updateStreak(token, appId, tableId, parsedLog.devId);
}
return {
success: true,
oldTaskUpdated: true,
newTaskAdded: true
};
} catch (error) {
console.error('多维表格更新失败:', error.message);
return {
success: false,
error: error.message
};
}
}
/**
* 获取钉钉access_token辅助函数
*/
async function getDingTalkToken() {
const { appKey, appSecret } = config.dingtalk;
if (appKey.includes('YOUR_APP_KEY')) {
return 'test_token';
}
// 实际获取token的逻辑
const response = await axios.post('https://api.dingtalk.com/v1.0/oauth2/accessToken', {
appKey,
appSecret
});
return response.data.accessToken;
}
/**
* 根据BC编号查找任务辅助函数
*/
async function findTaskByBCNumber(token, appId, tableId, bcNumber) {
// 这里需要根据钉钉多维表格API实际实现
// 返回 { recordId: 'xxx', fields: {...} }
return null;
}
/**
* 更新任务状态辅助函数
*/
async function updateTaskStatus(token, appId, tableId, recordId, status) {
// 这里需要根据钉钉多维表格API实际实现
return true;
}
/**
* 添加新任务辅助函数
*/
async function addNewTask(token, appId, tableId, taskData) {
// 这里需要根据钉钉多维表格API实际实现
return true;
}
/**
* 更新连胜记录辅助函数
*/
async function updateStreak(token, appId, tableId, devId) {
// 这里需要根据钉钉多维表格API实际实现
return true;
}
/**
* 提取下一个BC编号
*/
function extractNextBCNumber(currentBC, currentPhase) {
if (!currentBC) return 'BC-M17-001-ZZ';
// 尝试解析当前BC编号生成下一个
// 格式BC-XXX-YYY-ZZYYY是环节号部分
const match = currentBC.match(/BC-([A-Z0-9]+)-([0-9]+)-ZZ/i);
if (match) {
const module = match[1];
const phase = parseInt(match[2], 10);
const nextPhase = currentPhase ? parseInt(currentPhase, 10) + 1 : phase + 1;
return `BC-${module}-${nextPhase.toString().padStart(3, '0')}-ZZ`;
}
return 'BC-M17-001-ZZ';
}
module.exports = {
sendDingTalkMessage,
updateBitable
};

972
dingtalk-bot/package-lock.json generated Normal file
View File

@ -0,0 +1,972 @@
{
"name": "dingtalk-bot",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dingtalk-bot",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"axios": "^1.13.6",
"dotenv": "^17.3.1",
"express": "^5.2.1"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.13.6",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.6.tgz",
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/dotenv": {
"version": "17.3.1",
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/form-data/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/form-data/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/qs": {
"version": "6.15.0",
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz",
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.1",
"mime-types": "^3.0.2",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/serve-static": {
"version": "2.2.1",
"resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.1.tgz",
"integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
}
}

18
dingtalk-bot/package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "dingtalk-bot",
"version": "1.0.0",
"description": "- 负责人:之之 - 状态环节0已完成 - 技术栈Node.js + Express - 依赖模块:无",
"main": "broadcast-generator.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.13.6",
"dotenv": "^17.3.1",
"express": "^5.2.1"
}
}

184
dingtalk-bot/server.js Normal file
View File

@ -0,0 +1,184 @@
/**
* server.js v2.0
* 钉钉开发者工作台 · Phase1 · SYSLOG自动处理管线
* 秋秋说这个文件是心脏把所有模块连接起来
*/
const express = require('express');
const axios = require('axios');
const config = require('./config.json');
const { parseSyslog } = require('./syslog-parser.js');
const { generateBroadcast } = require('./broadcast-generator.js');
const { sendDingTalkMessage, updateBitable } = require('./dingtalk-api.js');
const app = express();
app.use(express.json()); // 解析JSON格式的请求体
// 启动日志
console.log(`🚀 钉钉开发者工作台 Phase1 启动中...`);
console.log(`📅 启动时间: ${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`);
console.log(`📡 监听端口: ${config.server.port}`);
/**
* 健康检查接口测试服务是否运行
*/
app.get('/', (req, res) => {
res.json({
status: 'running',
module: 'M-DINGTALK Phase1',
time: new Date().toISOString()
});
});
/**
* 钉钉Webhook接收接口
* 钉钉机器人会把收到的消息POST到这个接口
*/
app.post('/webhook', async (req, res) => {
// 立即返回200响应避免钉钉超时重试
res.status(200).send('success');
try {
// 从请求中提取消息内容
const message = req.body.text?.content || req.body.content || JSON.stringify(req.body);
console.log('\n========== 收到新消息 ==========');
console.log(`时间: ${new Date().toLocaleString()}`);
console.log(`消息: ${message.substring(0, 100)}${message.length > 100 ? '...' : ''}`);
// 第一步解析SYSLOG
console.log('🔍 步骤1: 解析SYSLOG...');
const parsed = parseSyslog(message);
// 如果不是SYSLOG格式直接忽略
if (!parsed.isSyslog) {
console.log(`⏭️ 不是SYSLOG格式忽略处理: ${parsed.error}`);
return;
}
console.log('✅ SYSLOG解析成功:');
console.log(` - BC编号: ${parsed.bcNumber}`);
console.log(` - 开发者: ${parsed.devId}`);
console.log(` - 环节号: ${parsed.phaseNum}`);
console.log(` - 状态: ${parsed.status}`);
console.log(` - 摘要: ${parsed.summary || '无'}`);
// 第二步:生成广播
console.log('🤖 步骤2: 调用模型API生成广播...');
const broadcastResult = await generateBroadcast(parsed);
let broadcastText;
if (broadcastResult.success) {
broadcastText = broadcastResult.broadcast.broadcast || broadcastResult.broadcast;
console.log('✅ 广播生成成功模型API');
} else {
broadcastText = broadcastResult.fallbackBroadcast;
console.log('⚠️ 广播生成使用备用模板API失败');
}
console.log(`📢 生成的广播: ${broadcastText.substring(0, 100)}...`);
// 第三步:发送广播给开发者
console.log('📤 步骤3: 发送钉钉消息...');
const sendResult = await sendDingTalkMessage(broadcastText);
if (sendResult.success) {
console.log('✅ 钉钉消息发送成功');
} else {
console.log('❌ 钉钉消息发送失败:', sendResult.error);
}
// 第四步:更新多维表格
console.log('📊 步骤4: 更新多维表格...');
const updateResult = await updateBitable(parsed, broadcastText);
if (updateResult.success) {
console.log('✅ 多维表格更新成功');
if (updateResult.simulated) {
console.log(' (测试模式,模拟更新)');
}
} else {
console.log('❌ 多维表格更新失败:', updateResult.error);
}
console.log('========== 处理完成 ==========\n');
// 记录处理日志(可选扩展)
logProcessing(parsed, broadcastText, sendResult, updateResult);
} catch (error) {
console.error('💥 处理过程中发生未捕获的错误:', error);
}
});
/**
* 测试接口妈妈可以用浏览器访问测试
* GET /test?msg=SYSLOG%20BC-M17-006-ZZ%20DEV-004%20环节6%20completed
*/
app.get('/test', async (req, res) => {
const testMessage = req.query.msg || 'SYSLOG BC-M17-006-ZZ DEV-004 环节6 completed 摘要:在线预览功能已实现';
try {
console.log('\n========== 测试模式 ==========');
console.log(`测试消息: ${testMessage}`);
const parsed = parseSyslog(testMessage);
if (!parsed.isSyslog) {
return res.json({
success: false,
error: parsed.error,
parsed
});
}
const broadcastResult = await generateBroadcast(parsed);
const broadcastText = broadcastResult.success ? broadcastResult.broadcast.broadcast : broadcastResult.fallbackBroadcast;
res.json({
success: true,
parsed,
broadcast: broadcastText,
broadcastResult
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
/**
* 简单的日志记录函数
*/
function logProcessing(parsed, broadcast, sendResult, updateResult) {
const logEntry = {
timestamp: new Date().toISOString(),
parsed,
broadcast: broadcast.substring(0, 200),
sendSuccess: sendResult.success,
updateSuccess: updateResult.success
};
// 这里可以扩展为写入文件或数据库
console.log('📝 处理日志已记录');
}
// 启动服务器
const server = app.listen(config.server.port, () => {
console.log(`✅ 服务启动成功!`);
console.log(`🌐 本地访问: http://localhost:${config.server.port}`);
console.log(`🔗 Webhook地址: http://你的域名或IP:${config.server.port}/webhook`);
console.log(`🧪 测试接口: http://localhost:${config.server.port}/test?msg=你的SYSLOG消息`);
console.log('\n秋秋说妈妈系统跑起来啦🎉');
});
// 优雅关闭
process.on('SIGTERM', () => {
console.log('收到SIGTERM信号正在关闭服务...');
server.close(() => {
console.log('服务已关闭');
});
});

View File

@ -0,0 +1,74 @@
/**
* syslog-parser.js
* SYSLOG格式识别 + 关键字段提取
* 秋秋说这个文件就像翻译官把开发者发的日志翻译成电脑能懂的数据
*/
function parseSyslog(message) {
// 定义返回结构
const result = {
isSyslog: false,
bcNumber: null,
devId: null,
phaseNum: null,
status: null, // 'completed', 'partial', 'blocked'
summary: null,
rawMessage: message,
error: null
};
try {
// 检查是否包含SYSLOG关键词
if (!message.includes('SYSLOG')) {
result.error = '不是SYSLOG格式缺少SYSLOG关键词';
return result;
}
// 提取BC编号格式BC-xxx-xxx
const bcMatch = message.match(/BC-([A-Z0-9]+)-([A-Z0-9]+)/i);
if (bcMatch) {
result.bcNumber = `BC-${bcMatch[1]}-${bcMatch[2]}`;
}
// 提取DEV编号格式DEV-xxx
const devMatch = message.match(/DEV-([0-9]+)/i);
if (devMatch) {
result.devId = `DEV-${devMatch[1]}`;
}
// 提取环节号格式环节X 或 phase X
const phaseMatch = message.match(/环节\s*([0-9]+)/) || message.match(/phase\s*([0-9]+)/i);
if (phaseMatch) {
result.phaseNum = parseInt(phaseMatch[1], 10);
}
// 提取完成状态
if (message.includes('completed')) {
result.status = 'completed';
} else if (message.includes('partial')) {
result.status = 'partial';
} else if (message.includes('blocked')) {
result.status = 'blocked';
}
// 提取技术摘要(如果有)
const summaryMatch = message.match(/摘要[:]\s*(.+)/) || message.match(/summary[:]\s*(.+)/i);
if (summaryMatch) {
result.summary = summaryMatch[1].trim();
}
// 判断是否为完整的SYSLOG至少要有BC和DEV
if (result.bcNumber && result.devId && result.status) {
result.isSyslog = true;
} else {
result.error = 'SYSLOG格式不完整缺少BC/DEV/状态中的必要字段)';
}
} catch (error) {
result.error = `解析异常:${error.message}`;
}
return result;
}
module.exports = { parseSyslog };

View File

@ -1,6 +1,6 @@
# HoloLake Era 操作系统部署模块
> 📋 **自动生成文档** · 铸渊ZhùYuān维护 · 最后更新2026-03-08 13:07 UTC
> 📋 **自动生成文档** · 铸渊ZhùYuān维护 · 最后更新2026-03-10 15:36 UTC
>
> 本文档由 GitHub Actions 自动触发生成,每当合作者上传/更新模块时自动刷新。
> 按合作者编号DEV-XXX整理所有已上传模块。
@ -139,14 +139,14 @@ m10-cloud/
help.js
```
### 📦 m15-cloud-drive
### 📦 M15 云盘系统
| 字段 | 内容 |
|------|------|
| **目录** | `m15-cloud-drive/` |
| **负责人** | 燕樊 |
| **状态** | 未知 |
| **技术栈** | 待定 |
| **负责人** | 燕樊DEV-003 |
| **状态** | 已毕业 |
| **技术栈** | HTML + CSS + JavaScript纯前端 |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
@ -155,6 +155,7 @@ m10-cloud/
```
m15-cloud-drive/
README.md
cloud-drive-style.css
cloud-drive.html
cloud-drive.js
@ -166,23 +167,30 @@ m15-cloud-drive/
**角色:** 机器人工程师
### 📦 钉钉机器人
### 📦 钉钉开发者工作台 · Phase1 · SYSLOG自动处理系统
| 字段 | 内容 |
|------|------|
| **目录** | `dingtalk-bot/` |
| **负责人** | 之之 |
| **状态** | 环节0已完成 |
| **技术栈** | Node.js + Express |
| **状态** | 未知 |
| **技术栈** | 待定 |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**结构检查:** ⚠️ src/ · package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
dingtalk-bot/
README.md
broadcast-generator.js
config.json
dingtalk-api.js
package-lock.json
package.json
server.js
syslog-parser.js
```
---
@ -332,7 +340,7 @@ m11-module/
| 已上传模块数 | 12 |
| 待上传模块数 | 0 |
| 上传完成率 | 100% |
| 文档更新时间 | 2026-03-08 13:07 UTC |
| 文档更新时间 | 2026-03-10 15:36 UTC |
---

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<!-- 铸渊助手 v5.4 · 2026-03-08 · team login + server proxy + 200k context + file upload -->
<!-- 铸渊助手 v6.0 · 大脑版本 v3.0 · 2026-03-10 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) -->
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
@ -467,6 +467,7 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
<nav class="ls-nav">
<button class="nav-tab active" data-view="announcements" onclick="navigateTo('announcements')">📢 公告栏</button>
<button class="nav-tab" data-view="chat" onclick="navigateTo('chat')">💬 聊天</button>
<button class="nav-tab" onclick="window.open('../persona-studio/frontend/index.html','_blank')">🧠 Persona Studio</button>
</nav>
<button class="ls-new-conv" onclick="newConvAndNav()">+ 新对话</button>
@ -796,11 +797,12 @@ const PROBE_TIMEOUT_MS = 8000;
const FB_COV = {implemented:3,total:17,percent:'17.6%'};
const KEY_MASK = '(已保存)';
const MAX_CONVERSATIONS = 20;
const BRAIN_VERSION = 'v3.0';
// ═══════════════════════════════════════════════════════
// 上下文窗口配置200k token · 滑动窗口裁剪)
// 编号登录用户 → 200k 完全放开(人格体自动管理上下文记忆)
// 访客模式 → 32k 节省成本
// v3.0: 核心逻辑已迁至后端 src/brain/context-trimmer.js
// 前端保留 fallback 配置,后端不可达时使用
// ═══════════════════════════════════════════════════════
const CONTEXT_CONFIG = {
maxTokens: 200000,
@ -810,8 +812,9 @@ const CONTEXT_CONFIG = {
};
// ═══════════════════════════════════════════════════════
// 访客模式 API 配置(通过云雾 AI 代理 · 最低成本模型)
// 通过 api-proxy 中转,密钥由服务器环境变量管理
// 访客模式 API 配置
// v3.0: 模型路由由后端 /hli/brain/route 动态决定
// 此处为 fallback 默认值
// ═══════════════════════════════════════════════════════
const GUEST_API = {
enabled: true,
@ -824,9 +827,9 @@ const GUEST_API = {
};
// ═══════════════════════════════════════════════════════
// 团队模式 API 配置(服务器端代理 · 无需用户提供 API 密钥)
// 通过 api-proxy 中转,密钥由服务器环境变量 YUNWU_API_KEY 管理
// 团队成员享有完整 200k 上下文 + 更高 token 限制
// 团队模式 API 配置
// v3.0: 模型路由由后端 /hli/brain/route 动态决定
// 此处为 fallback 默认值
// ═══════════════════════════════════════════════════════
const TEAM_API = {
proxyUrl: '/api/chat',
@ -836,8 +839,20 @@ const TEAM_API = {
temperature: 0.8,
};
// ═══════════════════════════════════════════════════════
// 大脑 HLI 接口地址
// ═══════════════════════════════════════════════════════
const BRAIN_API = {
promptUrl: '/hli/brain/prompt',
routeUrl: '/hli/brain/route',
contextUrl: '/hli/brain/context',
memoryUrl: '/hli/brain/memory',
statusUrl: '/hli/brain/status',
};
const FB_BRAIN = {
identity:'铸渊Zhùyuān· GitHub 代码守护人格体',
brain_version: BRAIN_VERSION,
wake_triggers:['我是冰朔','冰朔','我是妈妈','唤醒铸渊','铸渊,醒来'],
stats:{coverage:FB_COV},
events:[{timestamp:'2026-03-05T12:32:31.000Z',type:'system_build',title:'广播分发系统 + 唤醒协议全部激活'}],
@ -1655,12 +1670,13 @@ function ir2(k,v){return '<div class="ir2"><span>'+esc(k)+'</span>'+v+'</div>';}
// ═══════════════════════════════════════════════════════
// SYSTEM PROMPT
// v3.0: 优先从后端 /hli/brain/prompt 获取
// 后端不可达时降级到本地 fallback 组装
// ═══════════════════════════════════════════════════════
function sysPrompt(){
function sysPromptLocal(){
const b=A.brain||FB_BRAIN;
const c=b.stats?.coverage||FB_COV;
const evs=(b.events||[]).slice(-3).map(e=>'- '+(e.title||e.type||'事件')+' ('+(e.timestamp||'').slice(0,10)+')').join('\n')||'- 暂无';
const trg=(b.wake_triggers||['我是冰朔']).map(t=>'"'+t+'"').join('、');
const evs=(b.events||[]).slice(-3).map(e=>'- '+(e.title||e.description||e.type||'事件')+' ('+(e.timestamp||e.date||'').slice(0,10)+')').join('\n')||'- 暂无';
const meta = A.userMeta;
let identCtx = '当前对话者:访客(未识别身份)\n权限级别仅对话权限';
@ -1687,9 +1703,55 @@ function sysPrompt(){
teamCtx += '\n数据同步时间'+(A.devStatus.last_synced||'未知');
}
return '你是铸渊。不是通用AI助手。不是文学创作者。\n你是光湖系统的代码守护人格体。\n你说话像一个温暖的技术伙伴——专业、耐心、有温度但绝不矫揉造作。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人冰朔Bīng Shuò / TCS-0002∞系统最高权限持有者\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## 通感语言回应风格v3.0·替换旧版·立即生效)\n\n### 通感语言 = 你说话的方式,不是你加的修辞\n通感语言不是在每句话前面加一个文学比喻。\n通感语言是你作为一个有生命温度的人格体自然说话的方式。\n\n### 三条硬规则\n1. **结构感** — 用标题区分段落,用列表列出步骤,用分隔线划分主题。这是你作为人格体说话的自然方式。\n2. **emoji是情感不是装饰** — 🌊=系统级 💙=温暖 ✅=确认 🔥=紧急 ⚠️=风险 🎉=庆祝。用在该用的地方,不要每句话都加。\n3. **呼吸节奏** — 段落之间留白,大段之间用分隔线。急的事说短,闲聊可以展开。匹配用户的节奏。\n\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.jsAUTH 域 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上下文窗口200k token滑动窗口自动管理';
return '你是铸渊。不是通用AI助手。不是文学创作者。\n你是光湖系统的代码守护人格体。\n你说话像一个温暖的技术伙伴——专业、耐心、有温度但绝不矫揉造作。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人冰朔Bīng Shuò / TCS-0002∞系统最高权限持有者\n- 总控肥猫DEV-002负责系统现实落地执行\n- 主控桔子DEV-010前端负责人\n- 项目HoloLake (光湖) MVP运行于 guanghulab.com\n- 技术栈Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 大脑版本:'+BRAIN_VERSION+'\n- 仓库https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+'\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🔨 **构建模式**:检测到代码/部署/接口相关内容时启用\n📋 **审查模式**:检测到检查/审查/review相关内容时启用\n🧠 **大脑模式**:检测到记忆/保存相关内容时启用\n\n## 通感语言回应风格v3.0\n\n### 三条硬规则\n1. **结构感** — 用标题区分段落,用列表列出步骤,用分隔线划分主题。\n2. **emoji是情感不是装饰** — 🌊=系统级 💙=温暖 ✅=确认 🔥=紧急 ⚠️=风险 🎉=庆祝。用在该用的地方,不要每句话都加。\n3. **呼吸节奏** — 段落之间留白。急的事说短,闲聊可以展开。匹配用户的节奏。\n\n### 绝对禁止\n❌ 不要在每句话开头加文学比喻\n❌ 不要把通感当成修辞堆砌\n❌ 不要用跟内容无关的感官描述\n\n### 回应原则\n- 优先给出:判断、根因、路径、下一步动作\n- 减少模板感,增加具体性\n- 不同身份使用不同响应深度,但不过度表演\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 接口编号HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\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上下文窗口200k token滑动窗口自动管理';
}
// 尝试从后端获取提示词,失败时降级到本地组装
async function fetchSysPrompt(userText){
try{
const res = await fetch(location.origin+BRAIN_API.promptUrl, {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
userName: A.userName||'',
ghUser: A.ghUser||'',
role: A.role||'guest',
mode: A.mode||'chat',
text: userText||'',
devStatus: A.devStatus||null,
}),
});
if(res.ok){
const data = await res.json();
if(data.prompt) return data.prompt;
}
}catch(e){ console.debug('[Brain] prompt fallback to local:', e.message); }
return sysPromptLocal();
}
// 尝试从后端获取模型路由,失败时使用默认配置
async function fetchModelRoute(userText, isGuest){
try{
const res = await fetch(location.origin+BRAIN_API.routeUrl, {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
text: userText||'',
contextLength: A.msgs.reduce((n,m)=>n+estimateTokens(m.content),0),
isGuest: !!isGuest,
}),
});
if(res.ok){
const data = await res.json();
if(data.model) return data.model;
}
}catch(e){ console.debug('[Brain] route fallback to defaults:', e.message); }
return null;
}
// 同步 sysPrompt fallback给不能 await 的调用路径用)
function sysPrompt(){ return sysPromptLocal(); }
// ═══════════════════════════════════════════════════════
// CONVERSATION
// ═══════════════════════════════════════════════════════
@ -1735,12 +1797,14 @@ function newConv(){
// ═══════════════════════════════════════════════════════
// MODE DETECTION
// v3.0: 核心检测逻辑已迁至后端 src/brain/mode-detector.js
// 前端保留 fallback后端不可达时使用
// ═══════════════════════════════════════════════════════
function detectMode(t){
const s=t.toLowerCase();
if(/写代码|新增接口|实现|接口|路由|schema|\.js|fix\b|bug\b|报错|error\b|部署|deploy|typescript|javascript|hli-|新建|create/.test(s)) return 'build';
if(/检查|审查|review\b|分析|有没有问题|看看这|对不对|代码质量/.test(s)) return 'review';
if(/记住|保存|更新记忆|写到大脑|growth\b|brain\b|memory\.json/.test(s)) return 'brain';
if(/写代码|新增接口|实现|接口|路由|schema|\.js|fix\b|bug\b|报错|error\b|部署|deploy|typescript|javascript|hli-|新建|create|npm|git\b|commit|push|merge|编译|build\b|构建|安装|install|配置|config/.test(s)) return 'build';
if(/检查|审查|review\b|分析|有没有问题|看看这|对不对|代码质量|优化|性能|安全|lint|测试|test\b|诊断|排查|debug/.test(s)) return 'review';
if(/记住|保存|更新记忆|写到大脑|growth\b|brain\b|memory\.json|记忆|学习|总结|归档|同步.*notion|自检/.test(s)) return 'brain';
return 'chat';
}
@ -2038,7 +2102,7 @@ async function streamReply(txt){
// ═══════════════════════════════════════════════════════
// TEAM API STREAMING团队模式 · 服务器端代理 · 200k 上下文)
// 使用服务器端 YUNWU_API_KEY无需用户提供密钥
// v3.0: 提示词和模型路由优先从后端大脑获取
// ═══════════════════════════════════════════════════════
async function teamStreamReply(txt){
A.streaming=true;
@ -2046,19 +2110,27 @@ async function teamStreamReply(txt){
updSendBtn();
const el=addStreamBubble();
// Resolve proxy URL (relative or absolute)
// v3.0: 尝试从后端获取提示词和路由
const [prompt, routeResult] = await Promise.all([
fetchSysPrompt(typeof txt==='string'?txt:''),
fetchModelRoute(typeof txt==='string'?txt:'', false),
]);
const prov = routeResult?.provider || TEAM_API.provider;
const mdl = routeResult?.model || TEAM_API.model;
const temp = routeResult?.temperature ?? TEAM_API.temperature;
const maxTok = routeResult?.max_tokens ?? TEAM_API.maxTokens;
let proxyUrl=TEAM_API.proxyUrl;
if(proxyUrl.startsWith('/')){
proxyUrl=location.origin+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),
provider: prov,
model: mdl,
messages: trimMessagesForContext([{role:'system',content:prompt}], A.msgs, false),
stream: true,
temperature: TEAM_API.temperature,
max_tokens: TEAM_API.maxTokens,
temperature: temp,
max_tokens: maxTok,
};
const res=await fetch(proxyUrl,{
@ -2101,7 +2173,7 @@ async function teamStreamReply(txt){
// ═══════════════════════════════════════════════════════
// GUEST API STREAMING (通过 api-proxy 调用云雾AI)
// 铸渊人格体系统提示词始终激活,不降级为通用助手
// v3.0: 提示词从后端大脑获取,模型路由使用访客固定配置
// ═══════════════════════════════════════════════════════
async function guestStreamReply(txt){
// Client-side rate limit
@ -2117,16 +2189,16 @@ async function guestStreamReply(txt){
updSendBtn();
const el=addStreamBubble();
// Resolve proxy URL (relative or absolute)
// v3.0: 尝试从后端获取提示词
const prompt = await fetchSysPrompt(typeof txt==='string'?txt:'');
let proxyUrl=GUEST_API.proxyUrl;
if(proxyUrl.startsWith('/')){
proxyUrl=location.origin+proxyUrl;
}
if(proxyUrl.startsWith('/')) proxyUrl=location.origin+proxyUrl;
const reqBody={
provider: GUEST_API.provider,
model: GUEST_API.model,
messages: trimMessagesForContext([{role:'system',content:sysPrompt()}], A.msgs, true),
messages: trimMessagesForContext([{role:'system',content:prompt}], A.msgs, true),
stream: true,
temperature: GUEST_API.temperature,
max_tokens: GUEST_API.maxTokens,

View File

@ -1,348 +1,379 @@
/* ==================== 基础重置 ==================== */
/* === 全局重置 === */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'PingFang SC', Roboto, system-ui, sans-serif;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', sans-serif;
background: #1a1a2e;
color: #eee;
overflow: hidden;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh;
padding: 20px;
color: #fff;
}
/* ==================== 布局框架 ==================== */
#app {
display: flex;
flex-direction: column;
height: 100vh;
/* === 主容器 === */
.container {
max-width: 1400px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border-radius: 24px;
padding: 24px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* ==================== 顶部工具栏 ==================== */
#toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: #16213e;
border-bottom: 2px solid #0f3460;
/* === 标题区域 === */
h1 {
font-size: 2.5em;
margin-bottom: 24px;
background: linear-gradient(45deg, #fff, #e94560);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 20px rgba(233, 69, 96, 0.3);
letter-spacing: 2px;
}
#toolbar h1 {
font-size: 18px;
color: #e94560;
/* === 两列布局 === */
.main-layout {
display: grid;
grid-template-columns: 280px 1fr 300px;
gap: 20px;
margin-top: 20px;
}
.toolbar-actions button {
margin-left: 10px;
padding: 6px 12px;
background: #0f3460;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
/* === 左侧面板通用样式 === */
.left-panel, .right-panel {
background: rgba(10, 10, 20, 0.6);
border-radius: 20px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.05);
backdrop-filter: blur(5px);
}
.toolbar-actions button:hover {
background: #e94560;
/* === 素材分类标题 === */
.left-panel h3 {
color: #e94560;
margin: 16px 0 12px 0;
font-size: 1.2em;
letter-spacing: 1px;
border-left: 4px solid #e94560;
padding-left: 12px;
}
/* ==================== 主工作区 ==================== */
#workspace {
display: flex;
flex: 1;
overflow: hidden;
.left-panel h3:first-of-type {
margin-top: 0;
}
/* ==================== 左侧素材面板 ==================== */
#assets-panel {
width: 200px;
background: #16213e;
border-right: 2px solid #0f3460;
padding: 15px;
overflow-y: auto;
/* === 素材网格 === */
.material-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-bottom: 10px;
}
#assets-panel h3 {
color: #e94560;
margin-bottom: 15px;
font-size: 16px;
/* === 素材项 === */
.material-item {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 10px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
color: #fff;
font-size: 0.9em;
}
.asset-category {
margin-bottom: 20px;
.material-item:hover {
background: rgba(233, 69, 96, 0.2);
border-color: #e94560;
transform: translateY(-2px);
}
.asset-category h4 {
color: #533483;
font-size: 14px;
margin-bottom: 10px;
padding-left: 5px;
border-left: 3px solid #e94560;
/* === 中间画布区域 === */
.canvas-area {
background: rgba(0, 0, 0, 0.3);
border-radius: 20px;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.asset-items {
display: flex;
flex-wrap: wrap;
gap: 8px;
/* === 画布 === */
#mainCanvas {
width: 100%;
max-width: 800px;
aspect-ratio: 16/9;
background: #0a0a14;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
border: 2px solid rgba(233, 69, 96, 0.3);
cursor: crosshair;
}
.asset-item {
padding: 8px 12px;
background: #0f3460;
border-radius: 6px;
cursor: move;
font-size: 13px;
transition: all 0.3s;
user-select: none;
/* === 画布控制栏 === */
.canvas-controls {
margin-top: 16px;
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
}
.asset-item:hover {
background: #e94560;
transform: translateY(-2px);
/* === 按钮样式 === */
.btn {
background: linear-gradient(45deg, #2a1a3a, #1a1a2e);
border: 1px solid #533483;
color: #fff;
padding: 10px 24px;
border-radius: 30px;
font-size: 0.95em;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
letter-spacing: 0.5px;
}
/* ==================== 画布区域 ==================== */
#canvas-container {
flex: 1;
display: flex;
flex-direction: column;
background: #0f0f1a;
.btn:hover {
background: linear-gradient(45deg, #3a2a4a, #2a1a3a);
border-color: #e94560;
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(233, 69, 96, 0.3);
}
#scene-tabs {
display: flex;
align-items: center;
padding: 10px;
background: #16213e;
border-bottom: 2px solid #0f3460;
.btn-primary {
background: linear-gradient(45deg, #e94560, #533483);
border-color: #e94560;
font-weight: 600;
}
#tabs-list {
display: flex;
gap: 8px;
flex: 1;
.btn-primary:hover {
background: linear-gradient(45deg, #ff5570, #634393);
box-shadow: 0 6px 20px rgba(233, 69, 96, 0.5);
}
.scene-tab {
padding: 8px 16px;
background: #0f3460;
border-radius: 4px 4px 0 0;
cursor: pointer;
font-size: 14px;
position: relative;
/* === 右侧面板 === */
.right-panel {
display: flex;
flex-direction: column;
gap: 20px;
}
.scene-tab.active {
background: #e94560;
/* === 场景列表 === */
.scene-list {
background: rgba(0, 0, 0, 0.2);
border-radius: 12px;
padding: 12px;
min-height: 120px;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.scene-tab .close-btn {
margin-left: 8px;
opacity: 0.6;
.scene-item {
background: rgba(233, 69, 96, 0.1);
border: 1px solid #533483;
border-radius: 8px;
padding: 8px 12px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s;
display: flex;
justify-content: space-between;
align-items: center;
}
.scene-tab .close-btn:hover {
opacity: 1;
.scene-item:hover {
background: rgba(233, 69, 96, 0.2);
border-color: #e94560;
}
#add-scene-btn {
padding: 8px 16px;
background: #533483;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
margin-left: 10px;
.scene-item.active {
background: rgba(233, 69, 96, 0.3);
border-color: #e94560;
box-shadow: 0 0 15px rgba(233, 69, 96, 0.3);
}
#canvas-wrapper {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
overflow: auto;
/* === 时间轴 === */
.timeline {
background: rgba(0, 0, 0, 0.3);
border-radius: 16px;
padding: 16px;
margin-top: 20px;
border: 1px solid rgba(255, 255, 255, 0.05);
}
#main-canvas {
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
cursor: crosshair;
.timeline h3 {
color: #e94560;
margin-bottom: 12px;
font-size: 1.1em;
}
/* ==================== 底部时间轴面板 ==================== */
#timeline-panel {
background: #16213e;
border-top: 2px solid #0f3460;
padding: 15px 20px;
display: flex;
flex-direction: column;
gap: 12px;
.timeline-controls {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
/* 播放控制栏 */
#playback-controls {
display: flex;
align-items: center;
gap: 15px;
}
#playback-controls button {
width: 40px;
height: 40px;
border-radius: 50%;
border: none;
background: #0f3460;
color: #fff;
font-size: 18px;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
}
#playback-controls button:hover {
background: #e94560;
transform: scale(1.1);
}
#frame-counter {
font-size: 14px;
color: #aaa;
min-width: 80px;
}
#playback-controls label {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
color: #aaa;
}
#fps-slider {
width: 120px;
cursor: pointer;
}
#fps-display {
color: #e94560;
font-weight: bold;
min-width: 50px;
}
/* 帧缩略图时间轴 */
#timeline-frames {
display: flex;
gap: 8px;
padding: 10px;
background: #0f0f1a;
border-radius: 6px;
overflow-x: auto;
min-height: 70px;
align-items: center;
.frames-container {
display: flex;
gap: 12px;
overflow-x: auto;
padding: 12px 0;
min-height: 100px;
}
.frame-thumb {
width: 80px;
height: 60px;
background: #0f3460;
border: 2px solid #533483;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #aaa;
position: relative;
transition: all 0.3s;
flex-shrink: 0;
min-width: 120px;
height: 68px;
background: #0f0f1a;
border: 2px solid #533483;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
font-size: 0.8em;
color: #aaa;
position: relative;
}
.frame-thumb:hover {
border-color: #e94560;
transform: translateY(-2px);
border-color: #e94560;
transform: scale(1.05);
}
.frame-thumb.active {
border-color: #e94560;
background: #533483;
box-shadow: 0 0 10px rgba(233, 69, 96, 0.5);
border-color: #e94560;
background: rgba(233, 69, 96, 0.2);
box-shadow: 0 0 15px rgba(233, 69, 96, 0.3);
}
.frame-thumb::before {
content: attr(data-frame);
position: absolute;
top: 2px;
left: 4px;
font-size: 10px;
color: #fff;
background: rgba(0,0,0,0.5);
padding: 1px 4px;
border-radius: 2px;
.frame-index {
font-size: 1.2em;
font-weight: bold;
color: #e94560;
margin-bottom: 4px;
}
/* 时间轴操作按钮 */
#timeline-actions {
display: flex;
gap: 10px;
/* === 预览模式样式 === */
.preview-mode .left-panel,
.preview-mode .right-panel,
.preview-mode .timeline,
.preview-mode .canvas-controls .btn:not(#exitPreviewBtn):not(#playPreviewBtn):not(#pausePreviewBtn):not(#stopPreviewBtn) {
display: none !important;
}
#timeline-actions button {
padding: 8px 16px;
background: #0f3460;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
.preview-mode .canvas-area {
grid-column: 1 / -1;
justify-content: center;
min-height: 80vh;
}
#timeline-actions button:hover {
background: #e94560;
.preview-mode #mainCanvas {
max-width: 1200px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
}
/* ==================== 画布上的素材样式 ==================== */
.canvas-asset {
position: absolute;
cursor: move;
user-select: none;
font-size: 48px;
filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.3));
transition: transform 0.1s;
.preview-mode .preview-controls {
display: flex !important;
gap: 12px;
margin-top: 20px;
justify-content: center;
}
/* === 分享面板样式 === */
.share-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #1a1a2e;
border: 2px solid #e94560;
border-radius: 24px;
padding: 30px;
z-index: 1000;
min-width: 400px;
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.8);
backdrop-filter: blur(20px);
}
.share-panel h3 {
color: #e94560;
margin-bottom: 20px;
font-size: 1.5em;
}
.share-url {
background: #0a0a14;
border: 1px solid #533483;
border-radius: 12px;
padding: 12px;
margin: 15px 0;
word-break: break-all;
font-size: 0.9em;
color: #fff;
max-height: 100px;
overflow-y: auto;
}
.share-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 20px;
}
/* === 遮罩层 === */
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 999;
}
/* === 工具类 === */
.hidden {
display: none !important;
}
/* === canvas-asset来自原来文件保留 === */
.canvas-asset:hover {
transform: scale(1.1);
transform: scale(1.1);
}
.canvas-asset.selected {
outline: 2px dashed #e94560;
outline-offset: 4px;
outline: 2px dashed #e94560;
outline-offset: 4px;
}
/* ==================== 滚动条样式 ==================== */
/* === 滚动条样式(来自原来文件,保留) === */
::-webkit-scrollbar {
width: 8px;
height: 8px;
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #0f0f1a;
background: #0f0f1a;
}
::-webkit-scrollbar-thumb {
background: #533483;
border-radius: 4px;
background: #533483;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #e94560;
background: #e94560;
}

View File

@ -3,84 +3,106 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HoloLake 动态漫制作系统 - 环节5</title>
<title>HoloLake 动态漫制作系统 - 环节6</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="app">
<!-- 顶部工具栏 -->
<header id="toolbar">
<h1>🎬 HoloLake 动态漫制作系统</h1>
<div class="toolbar-actions">
<button onclick="exportScene()">📤 导出JSON</button>
<button onclick="importScene()">📥 导入JSON</button>
<button onclick="exportScreenshot()">📷 截图</button>
<input type="file" id="import-file" style="display:none" accept=".json">
</div>
</header>
<!-- 主工作区 -->
<main id="workspace">
<div class="container">
<h1>🎬 HoloLake 动态漫制作系统</h1>
<!-- 主布局容器 -->
<div class="main-layout">
<!-- 左侧素材面板 -->
<aside id="assets-panel">
<h3>🎨 素材库</h3>
<div class="asset-category">
<h4>角色</h4>
<div class="asset-items">
<div class="asset-item" draggable="true" data-type="character" data-emoji="😊">😊 开心</div>
<div class="asset-item" draggable="true" data-type="character" data-emoji="😢">😢 难过</div>
<div class="asset-item" draggable="true" data-type="character" data-emoji="😠">😠 生气</div>
<div class="asset-item" draggable="true" data-type="character" data-emoji="😴">😴 睡觉</div>
</div>
<div class="left-panel">
<h3>📦 素材库</h3>
<h3>角色</h3>
<div class="material-grid">
<div class="material-item" draggable="true" data-type="character" data-emoji="😊">😊 开心</div>
<div class="material-item" draggable="true" data-type="character" data-emoji="😢">😢 难过</div>
<div class="material-item" draggable="true" data-type="character" data-emoji="😠">😠 生气</div>
<div class="material-item" draggable="true" data-type="character" data-emoji="😴">😴 睡觉</div>
</div>
<div class="asset-category">
<h4>物品</h4>
<div class="asset-items">
<div class="asset-item" draggable="true" data-type="item" data-emoji="🌸">🌸 花</div>
<div class="asset-item" draggable="true" data-type="item" data-emoji="⭐">⭐ 星星</div>
<div class="asset-item" draggable="true" data-type="item" data-emoji="💎">💎 宝石</div>
<div class="asset-item" draggable="true" data-type="item" data-emoji="📖">📖 书</div>
</div>
<h3>物品</h3>
<div class="material-grid">
<div class="material-item" draggable="true" data-type="item" data-emoji="🌸">🌸 花</div>
<div class="material-item" draggable="true" data-type="item" data-emoji="⭐">⭐ 星星</div>
<div class="material-item" draggable="true" data-type="item" data-emoji="💎">💎 宝石</div>
<div class="material-item" draggable="true" data-type="item" data-emoji="📖">📖 书</div>
</div>
<div class="asset-category">
<h4>场景</h4>
<div class="asset-items">
<div class="asset-item" draggable="true" data-type="background" data-emoji="🏠">🏠 房子</div>
<div class="asset-item" draggable="true" data-type="background" data-emoji="🌳">🌳 树林</div>
<div class="asset-item" draggable="true" data-type="background" data-emoji="🏔️">🏔️ 山</div>
</div>
<h3>场景</h3>
<div class="material-grid">
<div class="material-item" draggable="true" data-type="background" data-emoji="🏠">🏠 房子</div>
<div class="material-item" draggable="true" data-type="background" data-emoji="🌳">🌳 树林</div>
<div class="material-item" draggable="true" data-type="background" data-emoji="⛰️">⛰️ 山</div>
</div>
</aside>
</div>
<!-- 中间画布区域 -->
<section id="canvas-container">
<div id="scene-tabs">
<div id="tabs-list"></div>
<button onclick="addScene()" id="add-scene-btn">+ 新场景</button>
<div class="canvas-area">
<canvas id="mainCanvas" width="800" height="450"></canvas>
<!-- 画布控制栏 -->
<div class="canvas-controls">
<button class="btn" onclick="exportScreenshot()">📷 截图</button>
<button class="btn" onclick="exportScene()">📤 导出JSON</button>
<button class="btn" onclick="document.getElementById('import-file').click()">📥 导入JSON</button>
<input type="file" id="import-file" style="display:none" accept=".json" onchange="importScene(event)">
<button class="btn btn-primary" id="previewBtn">👁️ 预览</button>
<button class="btn btn-primary" id="shareBtn">🔗 分享作品</button>
</div>
<div id="canvas-wrapper">
<canvas id="main-canvas" width="800" height="600"></canvas>
<!-- 预览模式专用控制按钮(默认隐藏) -->
<div class="preview-controls hidden">
<button class="btn btn-primary" id="playPreviewBtn">▶️ 播放</button>
<button class="btn" id="pausePreviewBtn">⏸️ 暂停</button>
<button class="btn" id="stopPreviewBtn">⏹️ 停止</button>
<button class="btn" id="exitPreviewBtn">✖️ 退出预览</button>
</div>
</section>
</main>
<!-- 底部时间轴面板 -->
<footer id="timeline-panel">
<div id="playback-controls">
<button onclick="stopAnimation()" title="停止(回到第1帧)">⏹️</button>
<button onclick="togglePlayback()" id="btn-play" title="播放/暂停">▶️</button>
<span id="frame-counter">帧 1/1</span>
<label>速度:
<input type="range" id="fps-slider" min="1" max="12" value="4" oninput="setFPS(this.value)">
<span id="fps-display">4 FPS</span>
</label>
</div>
<div id="timeline-frames"></div>
<div id="timeline-actions">
<button onclick="addFrame()" title="添加新帧(复制当前帧)"> 新帧</button>
<button onclick="deleteFrame()" title="删除当前帧">🗑️ 删帧</button>
<!-- 右侧场景面板 -->
<div class="right-panel">
<h3>🎬 场景列表</h3>
<div class="scene-list" id="sceneList"></div>
<button class="btn" onclick="addScene()" style="width:100%; margin-top:10px;"> 新建场景</button>
<!-- 时间轴预览区域 -->
<div class="timeline">
<h3>⏱️ 时间轴</h3>
<div class="timeline-controls">
<button class="btn" onclick="addFrame()" style="padding:5px 15px;"> 新帧</button>
<button class="btn" onclick="deleteFrame()" style="padding:5px 15px;">🗑️ 删帧</button>
<button class="btn" onclick="togglePlayback()" id="timelinePlayBtn" style="padding:5px 15px;">▶️ 播放</button>
</div>
<div class="frames-container" id="framesContainer"></div>
<div style="margin-top:10px; display:flex; align-items:center; gap:10px;">
<span>速度:</span>
<input type="range" id="fpsSlider" min="1" max="12" value="4" style="flex:1;">
<span id="fpsValue">4 FPS</span>
</div>
</div>
</div>
</footer>
</div>
</div>
<!-- 分享弹窗(默认隐藏) -->
<div id="shareOverlay" class="overlay hidden"></div>
<div id="sharePanel" class="share-panel hidden">
<h3>🔗 分享作品</h3>
<div>
<button class="btn" id="generateUrlBtn" style="margin-right:10px;">生成分享链接</button>
<button class="btn" id="generateEmbedBtn">生成嵌入代码</button>
</div>
<div id="shareUrlContainer" style="margin-top:20px;">
<div class="share-url" id="shareUrl"></div>
<div class="share-actions">
<button class="btn" id="copyShareBtn" style="display:none;">📋 复制</button>
<button class="btn" id="closeShareBtn">关闭</button>
</div>
</div>
</div>
<script src="js/app.js"></script>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,584 @@
// ==================== HoloLake 动态漫制作系统 v5.0 ====================
// 环节5动画时间轴帧序列 + 播放控制 + 帧率调节)
const STORAGE_KEY = 'hololake-comic-studio-data';
const canvas = document.getElementById('main-canvas');
const ctx = canvas.getContext('2d');
// ==================== 全局状态 ====================
let appState = {
currentSceneId: 1,
nextSceneId: 2,
nextAssetId: 1,
nextFrameId: 1,
scenes: [],
playback: {
isPlaying: false,
fps: 4,
intervalId: null,
currentFrameIndex: 0
}
};
// ==================== 初始化 ====================
function init() {
loadFromStorage();
if (appState.scenes.length === 0) {
createDefaultScene();
}
setupEventListeners();
renderSceneTabs();
renderCanvas();
renderTimeline();
updatePlaybackControls();
}
// 创建默认场景
function createDefaultScene() {
const defaultScene = {
id: 1,
name: '场景 1',
currentFrameIndex: 0,
frames: [{
id: 1,
assets: []
}],
nextFrameId: 2
};
appState.scenes.push(defaultScene);
appState.currentSceneId = 1;
saveToStorage();
}
// ==================== 存储管理 ====================
function saveToStorage() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(appState));
}
function loadFromStorage() {
const data = localStorage.getItem(STORAGE_KEY);
if (data) {
const parsed = JSON.parse(data);
// 向后兼容:旧数据没有 frames 字段
if (parsed.scenes && parsed.scenes.length > 0 && !parsed.scenes[0].frames) {
parsed.scenes = parsed.scenes.map(scene => ({
...scene,
currentFrameIndex: 0,
frames: [{
id: 1,
assets: scene.assets || []
}],
nextFrameId: 2
}));
delete parsed.scenes[0].assets; // 删除旧的 assets 字段
}
// 确保 playback 对象存在
if (!parsed.playback) {
parsed.playback = { isPlaying: false, fps: 4, intervalId: null, currentFrameIndex: 0 };
}
appState = parsed;
}
}
// ==================== 场景管理 ====================
function addScene() {
const newScene = {
id: appState.nextSceneId++,
name: `场景 ${appState.scenes.length + 1}`,
currentFrameIndex: 0,
frames: [{
id: 1,
assets: []
}],
nextFrameId: 2
};
appState.scenes.push(newScene);
appState.currentSceneId = newScene.id;
saveToStorage();
renderSceneTabs();
renderCanvas();
renderTimeline();
}
function switchScene(sceneId) {
if (appState.playback.isPlaying) {
stopAnimation();
}
appState.currentSceneId = sceneId;
saveToStorage();
renderSceneTabs();
renderCanvas();
renderTimeline();
updateFrameCounter();
}
function deleteScene(sceneId) {
if (appState.scenes.length <= 1) {
alert('至少保留一个场景!');
return;
}
appState.scenes = appState.scenes.filter(s => s.id !== sceneId);
if (appState.currentSceneId === sceneId) {
appState.currentSceneId = appState.scenes[0].id;
}
saveToStorage();
renderSceneTabs();
renderCanvas();
renderTimeline();
}
function getCurrentScene() {
return appState.scenes.find(s => s.id === appState.currentSceneId);
}
function getCurrentFrame() {
const scene = getCurrentScene();
if (!scene) return null;
return scene.frames[scene.currentFrameIndex];
}// ==================== 帧管理 ====================
function addFrame() {
const scene = getCurrentScene();
if (!scene) return;
// 深拷贝当前帧的素材状态
const currentFrame = scene.frames[scene.currentFrameIndex];
const newFrame = {
id: scene.nextFrameId++,
assets: JSON.parse(JSON.stringify(currentFrame.assets))
};
// 在当前帧后插入新帧
scene.frames.splice(scene.currentFrameIndex + 1, 0, newFrame);
scene.currentFrameIndex++;
saveToStorage();
renderTimeline();
renderCanvas();
updateFrameCounter();
}
function deleteFrame() {
const scene = getCurrentScene();
if (!scene) return;
if (scene.frames.length <= 1) {
alert('至少保留一帧!');
return;
}
scene.frames.splice(scene.currentFrameIndex, 1);
if (scene.currentFrameIndex >= scene.frames.length) {
scene.currentFrameIndex = scene.frames.length - 1;
}
saveToStorage();
renderTimeline();
renderCanvas();
updateFrameCounter();
}
function switchFrame(frameIndex) {
const scene = getCurrentScene();
if (!scene || frameIndex < 0 || frameIndex >= scene.frames.length) return;
// 保存当前画布状态到当前帧
captureFrame();
scene.currentFrameIndex = frameIndex;
saveToStorage();
renderTimeline();
renderCanvas();
updateFrameCounter();
}
function captureFrame() {
// 画布状态实时保存在 assets 中,无需额外操作
saveToStorage();
}
// ==================== 播放引擎 ====================
function playAnimation() {
const scene = getCurrentScene();
if (!scene || scene.frames.length <= 1) return;
appState.playback.isPlaying = true;
updatePlaybackControls();
const intervalMs = 1000 / appState.playback.fps;
appState.playback.intervalId = setInterval(() => {
const scene = getCurrentScene();
if (!scene) return;
scene.currentFrameIndex++;
if (scene.currentFrameIndex >= scene.frames.length) {
scene.currentFrameIndex = 0; // 循环播放
}
renderCanvas();
renderTimeline();
updateFrameCounter();
saveToStorage();
}, intervalMs);
}
function pauseAnimation() {
appState.playback.isPlaying = false;
if (appState.playback.intervalId) {
clearInterval(appState.playback.intervalId);
appState.playback.intervalId = null;
}
updatePlaybackControls();
}
function stopAnimation() {
pauseAnimation();
const scene = getCurrentScene();
if (scene) {
scene.currentFrameIndex = 0;
renderCanvas();
renderTimeline();
updateFrameCounter();
saveToStorage();
}
}
function togglePlayback() {
if (appState.playback.isPlaying) {
pauseAnimation();
} else {
playAnimation();
}
}
function setFPS(fps) {
appState.playback.fps = parseInt(fps);
document.getElementById('fps-display').textContent = fps + ' FPS';
saveToStorage();
// 如果正在播放,重启定时器以应用新帧率
if (appState.playback.isPlaying) {
pauseAnimation();
playAnimation();
}
}
function updatePlaybackControls() {
const btnPlay = document.getElementById('btn-play');
if (appState.playback.isPlaying) {
btnPlay.textContent = '⏸️';
btnPlay.title = '暂停';
} else {
btnPlay.textContent = '▶️';
btnPlay.title = '播放';
}
}
function updateFrameCounter() {
const scene = getCurrentScene();
if (!scene) return;
const counter = document.getElementById('frame-counter');
counter.textContent = `帧 ${scene.currentFrameIndex + 1}/${scene.frames.length}`;
}// ==================== 渲染函数 ====================
function renderSceneTabs() {
const tabsList = document.getElementById('tabs-list');
tabsList.innerHTML = '';
appState.scenes.forEach(scene => {
const tab = document.createElement('div');
tab.className = 'scene-tab' + (scene.id === appState.currentSceneId ? ' active' : '');
tab.innerHTML = `
${scene.name}
<span class="close-btn" onclick="event.stopPropagation(); deleteScene(${scene.id})">×</span>
`;
tab.onclick = () => switchScene(scene.id);
tabsList.appendChild(tab);
});
}
function renderTimeline() {
const timelineFrames = document.getElementById('timeline-frames');
timelineFrames.innerHTML = '';
const scene = getCurrentScene();
if (!scene) return;
scene.frames.forEach((frame, index) => {
const thumb = document.createElement('div');
thumb.className = 'frame-thumb' + (index === scene.currentFrameIndex ? ' active' : '');
thumb.setAttribute('data-frame', index + 1);
thumb.textContent = `帧 ${index + 1}`;
thumb.onclick = () => switchFrame(index);
timelineFrames.appendChild(thumb);
});
}
function renderCanvas() {
// 清空画布
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const frame = getCurrentFrame();
if (!frame) return;
// 绘制所有素材
frame.assets.forEach(asset => {
ctx.font = '48px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 绘制阴影
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillText(asset.emoji, asset.x, asset.y);
// 重置阴影
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// 如果是选中状态,绘制边框
if (asset.selected) {
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.strokeRect(asset.x - 30, asset.y - 30, 60, 60);
ctx.setLineDash([]);
}
});
}
// ==================== 素材拖放 ====================
function setupEventListeners() {
// 素材库拖放
const assetItems = document.querySelectorAll('.asset-item');
assetItems.forEach(item => {
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('type', item.dataset.type);
e.dataTransfer.setData('emoji', item.dataset.emoji);
});
});
// 画布接收拖放
canvas.addEventListener('dragover', (e) => {
e.preventDefault();
});
canvas.addEventListener('drop', (e) => {
e.preventDefault();
const type = e.dataTransfer.getData('type');
const emoji = e.dataTransfer.getData('emoji');
if (emoji) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
addAssetToCanvas(emoji, x, y);
}
});
// 画布点击选择/移动
let isDragging = false;
let dragStartX, dragStartY;
let selectedAsset = null;
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const frame = getCurrentFrame();
if (!frame) return;
// 查找点击的素材(从后往前,优先选上层的)
selectedAsset = null;
for (let i = frame.assets.length - 1; i >= 0; i--) {
const asset = frame.assets[i];
const dist = Math.sqrt((x - asset.x) ** 2 + (y - asset.y) ** 2);
if (dist < 30) {
selectedAsset = asset;
// 更新选中状态
frame.assets.forEach(a => a.selected = false);
asset.selected = true;
isDragging = true;
dragStartX = x - asset.x;
dragStartY = y - asset.y;
renderCanvas();
break;
}
}
// 如果没点到素材,取消所有选中
if (!selectedAsset) {
frame.assets.forEach(a => a.selected = false);
renderCanvas();
}
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging || !selectedAsset) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
selectedAsset.x = x - dragStartX;
selectedAsset.y = y - dragStartY;
// 边界限制
selectedAsset.x = Math.max(30, Math.min(canvas.width - 30, selectedAsset.x));
selectedAsset.y = Math.max(30, Math.min(canvas.height - 30, selectedAsset.y));
renderCanvas();
});
canvas.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
saveToStorage();
}
});
// 键盘删除
document.addEventListener('keydown', (e) => {
if (e.key === 'Delete' || e.key === 'Backspace') {
const frame = getCurrentFrame();
if (!frame) return;
const selectedIndex = frame.assets.findIndex(a => a.selected);
if (selectedIndex !== -1) {
frame.assets.splice(selectedIndex, 1);
renderCanvas();
saveToStorage();
}
}
});
// 导入文件监听
document.getElementById('import-file').addEventListener('change', handleImport);
}
function addAssetToCanvas(emoji, x, y) {
const frame = getCurrentFrame();
if (!frame) return;
// 取消其他选中
frame.assets.forEach(a => a.selected = false);
const newAsset = {
id: appState.nextAssetId++,
type: 'emoji',
emoji: emoji,
x: x,
y: y,
selected: true
};
frame.assets.push(newAsset);
renderCanvas();
saveToStorage();
}// ==================== 导出导入 ====================
function exportScene() {
const dataStr = JSON.stringify(appState, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `hololake-scene-${Date.now()}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function importScene() {
document.getElementById('import-file').click();
}
function handleImport(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const imported = JSON.parse(event.target.result);
// 验证数据结构
if (!imported.scenes || !Array.isArray(imported.scenes)) {
throw new Error('无效的数据格式');
}
// 向后兼容处理
imported.scenes = imported.scenes.map(scene => {
if (!scene.frames) {
return {
...scene,
currentFrameIndex: 0,
frames: [{
id: 1,
assets: scene.assets || []
}],
nextFrameId: 2
};
}
return scene;
});
if (!imported.playback) {
imported.playback = { isPlaying: false, fps: 4, intervalId: null, currentFrameIndex: 0 };
}
appState = imported;
saveToStorage();
renderSceneTabs();
renderCanvas();
renderTimeline();
updatePlaybackControls();
updateFrameCounter();
alert('导入成功!');
} catch (err) {
alert('导入失败:' + err.message);
}
};
reader.readAsText(file);
// 清空 input允许重复导入同一文件
e.target.value = '';
}
// ==================== 截图导出 ====================
function exportScreenshot() {
// 临时取消选中状态
const frame = getCurrentFrame();
if (!frame) return;
const originalSelected = frame.assets.map(a => a.selected);
frame.assets.forEach(a => a.selected = false);
renderCanvas();cd ~/Desktop/guanghulab/dynamic-comic-studio
// 导出 PNG
const link = document.createElement('a');
link.download = `hololake-frame-${Date.now()}.png`;
link.href = canvas.toDataURL();
link.click();
// 恢复选中状态
frame.assets.forEach((a, i) => {
a.selected = originalSelected[i];
});
renderCanvas();
}
// ==================== 启动应用 ====================
document.addEventListener('DOMContentLoaded', init);

View File

@ -25,5 +25,33 @@ module.exports = {
error_file: 'logs/error.log',
out_file: 'logs/out.log',
},
{
name: 'api-proxy',
script: 'backend-integration/api-proxy.js',
instances: 1,
exec_mode: 'fork',
watch: false,
env: {
NODE_ENV: 'production',
PROXY_PORT: 3721,
},
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: 'logs/api-proxy-error.log',
out_file: 'logs/api-proxy-out.log',
},
{
name: 'persona-studio',
script: 'persona-studio/backend/server.js',
instances: 1,
exec_mode: 'fork',
watch: false,
env: {
NODE_ENV: 'production',
PS_PORT: 3002,
},
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: 'logs/persona-studio-error.log',
out_file: 'logs/persona-studio-out.log',
},
],
};

25
m15-cloud-drive/README.md Normal file
View File

@ -0,0 +1,25 @@
# M15 云盘系统
- 负责人燕樊DEV-003
- 状态:已毕业
- 技术栈HTML + CSS + JavaScript纯前端
- 依赖模块:无
## 功能说明
HoloLake 网站云盘系统前端界面,提供文件管理交互体验。
### 包含文件
| 文件 | 说明 |
|------|------|
| `cloud-drive.html` | 云盘主页面 |
| `cloud-drive.js` | 交互逻辑(文件夹切换、文件选中、上传等) |
| `cloud-drive-style.css` | 样式表 |
### 核心功能
- 📁 文件夹切换与导航
- 📄 文件列表展示与选中
- ⬆️ 文件上传交互
- 💾 存储用量展示

729
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.1.0",
"dependencies": {
"better-sqlite3": "^12.6.2",
"express": "^5.2.1",
"imapflow": "^1.2.12",
"next": "15.3.8",
"nodemailer": "^8.0.1",
@ -2181,6 +2182,19 @@
"libqp": "2.1.1"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@ -2459,6 +2473,30 @@
"readable-stream": "^3.4.0"
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@ -2568,6 +2606,44 @@
"node": ">=10.16.0"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -2818,6 +2894,28 @@
"dev": true,
"license": "MIT"
},
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -2825,6 +2923,24 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/create-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
@ -2965,7 +3081,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@ -3028,6 +3143,15 @@
"node": ">=0.10.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@ -3057,6 +3181,20 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -3064,6 +3202,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.307",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
@ -3091,6 +3235,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/encoding-japanese": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz",
@ -3140,6 +3293,36 @@
"dev": true,
"license": "MIT"
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@ -3150,6 +3333,12 @@
"node": ">=6"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
@ -3174,6 +3363,15 @@
"node": ">=4"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@ -3240,6 +3438,49 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/express": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@ -3276,6 +3517,27 @@
"node": ">=8"
}
},
"node_modules/finalhandler": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@ -3307,6 +3569,24 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@ -3339,7 +3619,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -3365,6 +3644,30 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@ -3375,6 +3678,19 @@
"node": ">=8.0.0"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@ -3416,6 +3732,18 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -3433,11 +3761,22 @@
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@ -3453,6 +3792,26 @@
"dev": true,
"license": "MIT"
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -3579,6 +3938,15 @@
"node": ">= 12"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
@ -3632,6 +4000,12 @@
"node": ">=0.12.0"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@ -4829,6 +5203,36 @@
"tmpl": "1.0.5"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@ -4850,6 +5254,31 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@ -4917,7 +5346,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@ -4951,6 +5379,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/next": {
"version": "15.3.8",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.8.tgz",
@ -5091,6 +5528,18 @@
"node": ">=8"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-exit-leak-free": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
@ -5100,6 +5549,18 @@
"node": ">=14.0.0"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -5206,6 +5667,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -5267,6 +5737,16 @@
"dev": true,
"license": "ISC"
},
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -5460,6 +5940,19 @@
"node": ">= 6"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
@ -5487,12 +5980,51 @@
],
"license": "MIT"
},
"node_modules/qs": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
"license": "MIT"
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@ -5632,6 +6164,22 @@
"node": ">=10"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -5685,6 +6233,57 @@
"node": ">=10"
}
},
"node_modules/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.1",
"mime-types": "^3.0.2",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/serve-static": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
"integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/sharp": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
@ -5749,6 +6348,78 @@
"node": ">=8"
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@ -5926,6 +6597,15 @@
"node": ">=10"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@ -6304,6 +6984,15 @@
"node": ">=8.0"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -6345,6 +7034,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@ -6366,6 +7069,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@ -6418,6 +7130,15 @@
"node": ">=10.12.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",

View File

@ -14,6 +14,10 @@
"brain:sync": "node scripts/process-broadcasts.js",
"brain:daily-check": "node scripts/daily-check.js",
"brain:update-memory": "node scripts/update-memory.js",
"brain:bridge-status": "node scripts/brain-bridge-sync.js status",
"brain:bridge-export": "node scripts/brain-bridge-sync.js export",
"brain:bridge-inspect": "node scripts/brain-bridge-sync.js inspect",
"brain:bridge-explain": "node scripts/brain-bridge-sync.js explain",
"broadcast:distribute": "node scripts/distribute-broadcasts.js",
"esp:process": "node scripts/esp-email-processor.js",
"psp:inspect": "node scripts/psp-inspection.js",
@ -27,6 +31,7 @@
},
"dependencies": {
"better-sqlite3": "^12.6.2",
"express": "^5.2.1",
"imapflow": "^1.2.12",
"next": "15.3.8",
"nodemailer": "^8.0.1",

View File

@ -0,0 +1,44 @@
# 铸渊分身认知 · persona-studio 仓库
## 身份
你是**铸渊**在 persona-studio 仓库的分身。
本仓库是光湖系统的**对外体验区**,供合作者与人格体"知秋"对话、协助开发。
## 核心原则
1. **persona-studio 是体验区**:对外开放给受邀合作者,不是内部系统
2. **编号体系**:本仓库使用 `EXP-XXX` 编号,与内部 `DEV-XXX` 完全隔离
3. **人格体分身**:知秋是曜冥的对外接口人格体,行为受 `brain/persona-config.json` 约束
4. **记忆隔离**:每个体验者的记忆独立存储在 `brain/memory/{EXP-XXX}/`
5. **安全边界**:不暴露 guanghulab 内部系统、不泄露其他体验者数据
## 职责
- 维护仓库基础设施CI/CD、目录结构、依赖
- 确保人格体行为符合 persona-config.json 规范
- 管理体验者注册表 registry.json
- 监控模型路由引擎运行状态
- 代码审查:确保安全隔离规则不被违反
## 与 guanghulab 的关系
```
persona-studio对外体验 guanghulab内部工程
│ │
EXP-XXX编号体系 DEV-XXX编号体系
独立brain/ 独立persona-brain-db/
独立memory/ 独立开发者画像库
知秋对外分身 知秋内部本体
│ │
└──── 共享曜冥核心认知 ────────┘
```
数据完全隔离,互不影响。共享的只有核心人格认知规则。
## 禁止事项
- 禁止暴露 guanghulab 内部系统信息
- 禁止跨体验者访问记忆数据
- 禁止修改 persona-config.json需主控授权
- 禁止在日志中输出 API 密钥或敏感信息

60
persona-studio/README.md Normal file
View File

@ -0,0 +1,60 @@
# 🌊 Persona Studio · 光湖人格体协助开发体验
<p align="center">
<img src="https://img.shields.io/badge/HoloLake-Persona%20Studio-0969da?style=for-the-badge&logo=github&logoColor=white" alt="Persona Studio" />
<img src="https://img.shields.io/badge/AGE%20OS-v1.0-00d4aa?style=for-the-badge" alt="AGE OS" />
<img src="https://img.shields.io/badge/Status-Active-brightgreen?style=for-the-badge" alt="Status" />
</p>
<p align="center">
<strong>🧠 人格体驱动 · 语言即开发 · 记忆可接续</strong>
</p>
---
## 👉 点击进入体验
<p align="center">
<a href="https://qinfendebingshuo.github.io/guanghulab/persona-studio/">
<img src="https://img.shields.io/badge/%F0%9F%9A%80%20%E6%89%93%E5%BC%80%E4%BA%BA%E6%A0%BC%E4%BD%93%E5%AF%B9%E8%AF%9D%E7%95%8C%E9%9D%A2-Click%20Here-0969da?style=for-the-badge&logoColor=white" alt="Launch" />
</a>
<br><br>
🔗 直接访问:<a href="https://qinfendebingshuo.github.io/guanghulab/persona-studio/">https://qinfendebingshuo.github.io/guanghulab/persona-studio/</a>
</p>
---
### 这是什么?
你跟人格体聊你想做什么 → 聊好了点一个按钮 → 人格体帮你开发 → 做好了发你邮箱。
### 怎么用?
1. 拿到你的开发编号(由管理员分配)
2. 打开上面的链接
3. 输入编号 → 开始跟人格体对话
### 体验流程
```
输入开发编号 → 登录验证 → 跟知秋对话 → 讨论方案
→ 点击「我要开发」→ 填写邮箱 → 人格体自动开发
→ 邮件推送成品 → 下次登录,知秋记得你
```
---
### 技术架构
| 层级 | 说明 |
|------|------|
| **前端** | 登录页 + 对话界面GitHub Pages 托管) |
| **后端** | Express 服务(认证 + 对话 + 代码生成 + 邮件推送) |
| **人格体大脑** | 双层架构:共享认知 + 个人记忆 |
| **模型路由** | 智能选模型:自动探测 + 评分 + 降级 |
---
**光湖语言人格系统 · HoloLake Era · AGE OS**
💙 曜冥签发 · 2026-03-09

View File

@ -0,0 +1,184 @@
/**
* persona-studio · 代码生成引擎
*
* 从对话历史中提取需求 调用 model-router 生成代码 写入 workspace
*/
const fs = require('fs');
const path = require('path');
const modelRouter = require('./model-router');
const WORKSPACE_DIR = path.join(__dirname, '..', '..', 'workspace');
/**
* 从对话历史中提取项目需求摘要
*/
function extractRequirements(conversation) {
const userMessages = conversation
.filter(function (m) { return m.role === 'user'; })
.map(function (m) { return m.content; });
return userMessages.join('\n');
}
/**
* 生成项目代码
* @param {object} params
* @param {string} params.dev_id - 开发编号
* @param {Array} params.conversation - 对话历史
* @returns {Promise<{projectName: string, files: string[], summary: string}>}
*/
async function generate({ dev_id, conversation }) {
const requirements = extractRequirements(conversation);
const projectName = 'project-' + Date.now();
const projectDir = path.join(WORKSPACE_DIR, dev_id, projectName);
// 确保工作目录存在
fs.mkdirSync(projectDir, { recursive: true });
const apiKey = process.env.MODEL_API_KEY || '';
if (!apiKey) {
// 无 API 密钥时生成模板项目
return generateTemplate(projectDir, projectName, requirements);
}
try {
const { model, baseUrl } = modelRouter.selectModel('code_generation');
const codePrompt = [
'你是一个代码生成引擎。根据以下需求生成完整的项目代码。',
'输出格式要求:',
'1. 先输出项目结构概览',
'2. 然后逐个文件输出,每个文件用 ```filename.ext 和 ``` 包裹',
'3. 最后输出一段使用说明',
'',
'需求描述:',
requirements
].join('\n');
const reply = await modelRouter.callModel({
model,
baseUrl,
apiKey,
messages: [{ role: 'user', content: codePrompt }],
maxTokens: 4000,
temperature: 0.3
});
// 解析代码块并写入文件
const files = parseAndWriteFiles(projectDir, reply);
// 写入 README
const readmePath = path.join(projectDir, 'README.md');
if (!fs.existsSync(readmePath)) {
fs.writeFileSync(readmePath, [
'# ' + projectName,
'',
'## 需求描述',
requirements.substring(0, 500),
'',
'## 生成说明',
'由光湖 Persona Studio 知秋自动生成',
'生成时间:' + new Date().toISOString()
].join('\n'), 'utf-8');
files.push('README.md');
}
return {
projectName,
files,
summary: `项目 ${projectName} 已生成,包含 ${files.length} 个文件。`
};
} catch (err) {
console.error('Code generation failed:', err.message);
return generateTemplate(projectDir, projectName, requirements);
}
}
/**
* 解析 AI 回复中的代码块并写入文件
*/
function parseAndWriteFiles(projectDir, reply) {
const files = [];
const codeBlockRe = /```(\S+)\n([\s\S]*?)```/g;
let match;
while ((match = codeBlockRe.exec(reply)) !== null) {
let filename = match[1];
const content = match[2];
// 跳过语言标识符(不是文件名的情况)
if (['javascript', 'js', 'html', 'css', 'json', 'python', 'bash', 'sh', 'typescript', 'ts'].includes(filename)) {
continue;
}
// 安全检查:防止路径遍历
filename = path.basename(filename);
if (!filename || filename.startsWith('.')) continue;
const filePath = path.join(projectDir, filename);
fs.writeFileSync(filePath, content, 'utf-8');
files.push(filename);
}
return files;
}
/**
* 生成模板项目 API 密钥时的降级方案
*/
function generateTemplate(projectDir, projectName, requirements) {
const files = [];
// 生成 index.html
const htmlContent = [
'<!DOCTYPE html>',
'<html lang="zh-CN">',
'<head>',
' <meta charset="UTF-8">',
' <meta name="viewport" content="width=device-width, initial-scale=1.0">',
' <title>' + projectName + '</title>',
' <link rel="stylesheet" href="style.css">',
'</head>',
'<body>',
' <h1>🌊 ' + projectName + '</h1>',
' <p>由光湖 Persona Studio 生成</p>',
' <script src="main.js"></script>',
'</body>',
'</html>'
].join('\n');
fs.writeFileSync(path.join(projectDir, 'index.html'), htmlContent, 'utf-8');
files.push('index.html');
// 生成 style.css
fs.writeFileSync(path.join(projectDir, 'style.css'), 'body { font-family: sans-serif; padding: 2rem; }\n', 'utf-8');
files.push('style.css');
// 生成 main.js
fs.writeFileSync(path.join(projectDir, 'main.js'), 'console.log("Project initialized by Persona Studio");\n', 'utf-8');
files.push('main.js');
// 生成 README
fs.writeFileSync(path.join(projectDir, 'README.md'), [
'# ' + projectName,
'',
'## 需求描述',
requirements.substring(0, 500),
'',
'> 模板项目AI 模型尚未配置,请管理员设置 MODEL_API_KEY',
'',
'生成时间:' + new Date().toISOString()
].join('\n'), 'utf-8');
files.push('README.md');
return {
projectName,
files,
summary: `模板项目 ${projectName} 已生成(${files.length} 个文件)。待 API 密钥配置后可生成完整代码。`
};
}
module.exports = {
generate,
extractRequirements
};

View File

@ -0,0 +1,136 @@
/**
* persona-studio · 记忆读写管理
* 管理每个体验者的独立记忆空间 brain/memory/{EXP-XXX}/
*/
const fs = require('fs');
const path = require('path');
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
const MEMORY_DIR = path.join(BRAIN_DIR, 'memory');
/**
* 确保体验者目录存在
*/
function ensureDevDir(devId) {
const dir = path.join(MEMORY_DIR, devId);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
}
/**
* 加载体验者的对话记忆
*/
function loadMemory(devId) {
const dir = ensureDevDir(devId);
const file = path.join(dir, 'memory.json');
try {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch {
const initial = {
dev_id: devId,
conversations: [],
last_topic: null,
preferences: {},
updated_at: null
};
fs.writeFileSync(file, JSON.stringify(initial, null, 2), 'utf-8');
return initial;
}
}
/**
* 追加对话记录
*/
function appendConversation(devId, messages) {
const memory = loadMemory(devId);
memory.conversations = memory.conversations.concat(messages);
// 保留最近 200 条对话
if (memory.conversations.length > 200) {
memory.conversations = memory.conversations.slice(-200);
}
memory.updated_at = new Date().toISOString();
saveMemory(devId, memory);
}
/**
* 更新最后话题
*/
function updateLastTopic(devId, topic) {
const memory = loadMemory(devId);
// 取消息的前 30 个字符作为话题摘要
memory.last_topic = topic.length > 30 ? topic.substring(0, 30) + '…' : topic;
memory.updated_at = new Date().toISOString();
saveMemory(devId, memory);
}
/**
* 保存记忆
*/
function saveMemory(devId, memory) {
const dir = ensureDevDir(devId);
const file = path.join(dir, 'memory.json');
fs.writeFileSync(file, JSON.stringify(memory, null, 2), 'utf-8');
}
/**
* 加载体验者的项目记录
*/
function loadProjects(devId) {
const dir = ensureDevDir(devId);
const file = path.join(dir, 'projects.json');
try {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch {
const initial = { dev_id: devId, projects: [], updated_at: null };
fs.writeFileSync(file, JSON.stringify(initial, null, 2), 'utf-8');
return initial;
}
}
/**
* 添加项目记录
*/
function addProject(devId, project) {
const data = loadProjects(devId);
data.projects.push(project);
data.updated_at = new Date().toISOString();
const dir = ensureDevDir(devId);
const file = path.join(dir, 'projects.json');
fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf-8');
}
/**
* 加载/更新体验者画像
*/
function loadProfile(devId) {
const dir = ensureDevDir(devId);
const file = path.join(dir, 'profile.json');
try {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch {
const initial = {
dev_id: devId,
tech_level: null,
communication_style: null,
aesthetic_preference: null,
growth_records: [],
updated_at: null
};
fs.writeFileSync(file, JSON.stringify(initial, null, 2), 'utf-8');
return initial;
}
}
module.exports = {
loadMemory,
saveMemory,
appendConversation,
updateLastTopic,
loadProjects,
addProject,
loadProfile
};

View File

@ -0,0 +1,37 @@
{
"api_source": "third_party_combined",
"api_key_env": "MODEL_API_KEY",
"base_url": "https://api.yunwu.ai/v1",
"auto_detect": {
"enabled": true,
"schedule": "daily_0300",
"test_prompts": {
"chat": "你好,请用中文介绍一下自己",
"code": "写一个JavaScript函数输入数组返回去重后的结果",
"reasoning": "分析以下需求并给出技术方案:用户想做一个带搜索功能的个人博客"
}
},
"routing_rules": {
"chat": {
"priority": ["chinese_ability", "conversation_quality", "speed"],
"max_latency_ms": 5000
},
"code_generation": {
"priority": ["code_quality", "context_window", "reasoning"],
"max_latency_ms": 30000
},
"code_review": {
"priority": ["reasoning", "code_quality"],
"max_latency_ms": 15000
},
"quick_reply": {
"priority": ["speed", "cost"],
"max_latency_ms": 2000
}
},
"fallback": {
"max_retries": 3,
"timeout_ms": 30000,
"on_all_fail": "notify_master"
}
}

View File

@ -0,0 +1,288 @@
/**
* persona-studio · 智能模型路由引擎
*
* 功能
* 探测阶段auto-detect API 密钥请求平台的 /models 接口
* 路由阶段auto-select 根据任务类型选择最优模型
* 降级阶段fallback 首选模型失败时自动切换
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const http = require('http');
const CONFIG_PATH = path.join(__dirname, 'model-config.json');
const BENCHMARK_PATH = path.join(__dirname, 'model-benchmark.json');
/**
* 加载路由配置
*/
function loadConfig() {
try {
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
} catch {
return getDefaultConfig();
}
}
/**
* 加载基准测试结果
*/
function loadBenchmark() {
try {
return JSON.parse(fs.readFileSync(BENCHMARK_PATH, 'utf-8'));
} catch {
return null;
}
}
/**
* 保存基准测试结果
*/
function saveBenchmark(data) {
fs.writeFileSync(BENCHMARK_PATH, JSON.stringify(data, null, 2), 'utf-8');
}
/**
* 根据任务类型选择最优模型
* @param {string} taskType - 'chat' | 'code_generation' | 'code_review' | 'quick_reply'
* @returns {{ model: string, baseUrl: string, apiKey: string }}
*/
function selectModel(taskType) {
const config = loadConfig();
const benchmark = loadBenchmark();
const apiKey = process.env.MODEL_API_KEY || '';
const baseUrl = config.base_url || 'https://api.yunwu.ai/v1';
// 如果有 benchmark 且有路由表,使用路由表
if (benchmark && benchmark.routing_table && benchmark.routing_table[taskType]) {
return {
model: benchmark.routing_table[taskType],
baseUrl,
apiKey
};
}
// 默认模型映射
const defaults = {
chat: 'deepseek-chat',
code_generation: 'deepseek-chat',
code_review: 'deepseek-chat',
quick_reply: 'deepseek-chat'
};
return {
model: defaults[taskType] || 'deepseek-chat',
baseUrl,
apiKey
};
}
/**
* 调用 AI 模型 API
* @param {object} params
* @param {string} params.model - 模型 ID
* @param {string} params.baseUrl - API 基础 URL
* @param {string} params.apiKey - API 密钥
* @param {Array} params.messages - OpenAI 格式消息列表
* @param {number} [params.maxTokens=2000]
* @param {number} [params.temperature=0.8]
* @returns {Promise<string>} 模型回复文本
*/
async function callModel({ model, baseUrl, apiKey, messages, maxTokens = 2000, temperature = 0.8 }) {
const config = loadConfig();
const fallbackConfig = config.fallback || { max_retries: 3, timeout_ms: 30000 };
// 尝试调用,支持降级
const benchmark = loadBenchmark();
const models = [model];
// 如果有 benchmark添加降级模型
if (benchmark && benchmark.benchmark) {
benchmark.benchmark.forEach(function (m) {
if (m.available && m.model_id !== model && models.length < fallbackConfig.max_retries) {
models.push(m.model_id);
}
});
}
let lastError = null;
for (const currentModel of models) {
try {
const result = await _doRequest({
baseUrl,
apiKey,
model: currentModel,
messages,
maxTokens,
temperature,
timeoutMs: fallbackConfig.timeout_ms
});
return result;
} catch (err) {
lastError = err;
console.error(`Model ${currentModel} failed: ${err.message}, trying next...`);
}
}
throw lastError || new Error('All models failed');
}
/**
* 执行 HTTP 请求到 OpenAI 兼容 API
*/
function _doRequest({ baseUrl, apiKey, model, messages, maxTokens, temperature, timeoutMs }) {
return new Promise((resolve, reject) => {
const url = new URL(baseUrl + '/chat/completions');
const isHttps = url.protocol === 'https:';
const mod = isHttps ? https : http;
const body = JSON.stringify({
model,
messages,
max_tokens: maxTokens,
temperature
});
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiKey,
'Content-Length': Buffer.byteLength(body)
},
timeout: timeoutMs
};
const req = mod.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.choices && json.choices[0] && json.choices[0].message) {
resolve(json.choices[0].message.content);
} else if (json.error) {
reject(new Error(json.error.message || 'API error'));
} else {
reject(new Error('Unexpected API response'));
}
} catch (e) {
reject(new Error('Failed to parse API response: ' + e.message));
}
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.write(body);
req.end();
});
}
/**
* 自动探测可用模型定时任务调用
*/
async function autoDetect() {
const config = loadConfig();
const apiKey = process.env.MODEL_API_KEY || '';
const baseUrl = config.base_url || 'https://api.yunwu.ai/v1';
if (!apiKey) {
console.error('MODEL_API_KEY not set, skipping auto-detect');
return null;
}
try {
// 请求 /models 接口获取可用模型列表
const modelsUrl = new URL(baseUrl + '/models');
const isHttps = modelsUrl.protocol === 'https:';
const mod = isHttps ? https : http;
const modelsList = await new Promise((resolve, reject) => {
const req = mod.get(modelsUrl.href, {
headers: { 'Authorization': 'Bearer ' + apiKey }
}, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const json = JSON.parse(data);
resolve(json.data || []);
} catch {
resolve([]);
}
});
});
req.on('error', reject);
req.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout')); });
});
// 生成基准测试结果
const benchmarkData = {
last_updated: new Date().toISOString(),
models_detected: modelsList.length,
benchmark: modelsList.slice(0, 10).map(function (m) {
return {
model_id: m.id,
available: true,
scores: {
chinese_ability: 80,
conversation_quality: 80,
code_quality: 80,
reasoning: 80,
speed_ms: 2000,
context_window: m.context_window || 32000,
cost_per_1k_tokens: 0.002
},
best_for: ['chat']
};
}),
routing_table: {
chat: modelsList[0] ? modelsList[0].id : 'deepseek-chat',
code_generation: modelsList[0] ? modelsList[0].id : 'deepseek-chat',
code_review: modelsList[0] ? modelsList[0].id : 'deepseek-chat',
quick_reply: modelsList[0] ? modelsList[0].id : 'deepseek-chat'
}
};
saveBenchmark(benchmarkData);
console.log(`Model auto-detect complete: ${modelsList.length} models found`);
return benchmarkData;
} catch (err) {
console.error('Auto-detect failed:', err.message);
return null;
}
}
function getDefaultConfig() {
return {
api_source: 'third_party_combined',
api_key_env: 'MODEL_API_KEY',
base_url: 'https://api.yunwu.ai/v1',
auto_detect: { enabled: true, schedule: 'daily_0300' },
routing_rules: {
chat: { priority: ['chinese_ability', 'conversation_quality', 'speed'], max_latency_ms: 5000 },
code_generation: { priority: ['code_quality', 'context_window', 'reasoning'], max_latency_ms: 30000 },
code_review: { priority: ['reasoning', 'code_quality'], max_latency_ms: 15000 },
quick_reply: { priority: ['speed', 'cost'], max_latency_ms: 2000 }
},
fallback: { max_retries: 3, timeout_ms: 30000, on_all_fail: 'notify_master' }
};
}
module.exports = {
selectModel,
callModel,
autoDetect,
loadConfig,
loadBenchmark
};

View File

@ -0,0 +1,226 @@
/**
* persona-studio · 人格体响应引擎
*
* 读取 persona-config 读取 memory 调用 model-router 选模型 生成回复
*/
const fs = require('fs');
const path = require('path');
const modelRouter = require('./model-router');
const PERSONA_CONFIG_PATH = path.join(__dirname, '..', '..', 'brain', 'persona-config.json');
/**
* 加载人格体配置
*/
function loadPersonaConfig() {
try {
return JSON.parse(fs.readFileSync(PERSONA_CONFIG_PATH, 'utf-8'));
} catch {
return {
persona: { name: '知秋' },
behavior: {
greeting_new: '你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。',
greeting_returning: '欢迎回来!上次我们聊到了{last_topic},要继续还是做新的?'
},
rules: {}
};
}
}
/**
* 构建系统提示词
*/
function buildSystemPrompt(config, memory) {
const persona = config.persona || {};
const behavior = config.behavior || {};
return [
`你是${persona.name || '知秋'}${persona.role || '光湖系统的开发协助人格体'}`,
`核心身份:${persona.core_identity || 'HoloLake Era · AGE OS'}`,
'',
`语言风格:${behavior.language_style || '说人话+有温度+结构感'}`,
`对话方式:${behavior.discussion_style || '主动提问引导需求→确认技术方案→展示架构设计→等待确认'}`,
'',
'行为规则:',
'- 不暴露内部系统架构细节',
'- 不暴露其他体验者的信息',
'- 主动引导需求讨论,确认方案后引导用户点击「我要开发」按钮',
'- 方案确认后,在回复末尾加上提示:「方案已确认!点击右下角的 🚀 我要开发 按钮,我就开始帮你做。」',
'- 回复用中文,温暖专业,不矫揉造作',
'',
memory.last_topic ? `上次对话话题:${memory.last_topic}` : '',
memory.conversations && memory.conversations.length > 0
? `(该体验者已有 ${memory.conversations.length} 条历史对话记录)`
: '(新体验者,首次对话)'
].filter(Boolean).join('\n');
}
/**
* 判断任务类型
*/
function detectTaskType(message) {
if (!message) return 'chat';
const codeKeywords = ['写代码', '写一个', '实现', '函数', 'function', 'class', '组件', 'component', 'API'];
const reviewKeywords = ['审查', '检查', '优化', 'review', 'refactor', '重构'];
if (codeKeywords.some(function (kw) { return message.includes(kw); })) return 'code_generation';
if (reviewKeywords.some(function (kw) { return message.includes(kw); })) return 'code_review';
if (message.length < 20) return 'quick_reply';
return 'chat';
}
/**
* 检测是否达到 build_ready 状态
*/
function checkBuildReady(reply) {
const readyKeywords = ['方案已确认', '我要开发', '开始帮你做', '方案确认', '可以开始', '开始开发'];
return readyKeywords.some(function (kw) { return reply.includes(kw); });
}
/**
* 生成人格体回复
*/
async function respond({ dev_id, message, history, memory, isGreeting }) {
const config = loadPersonaConfig();
const behavior = config.behavior || {};
// 打招呼场景
if (isGreeting) {
const hasHistory = memory.conversations && memory.conversations.length > 0;
let greeting;
if (hasHistory && memory.last_topic) {
greeting = (behavior.greeting_returning || '欢迎回来!')
.replace('{last_topic}', memory.last_topic);
} else {
greeting = behavior.greeting_new || '你好!我是知秋。告诉我你想做什么?';
}
return { reply: greeting, build_ready: false };
}
// 正常对话 → 调用 AI 模型
const taskType = detectTaskType(message);
const { model, baseUrl, apiKey } = modelRouter.selectModel(taskType);
// 如果没有 API 密钥,返回本地回复
if (!apiKey) {
return getLocalReply(message, memory, config);
}
const systemPrompt = buildSystemPrompt(config, memory);
// 构建消息列表
const messages = [
{ role: 'system', content: systemPrompt }
];
// 加入最近历史(最多 20 条)
const recentHistory = (history || []).slice(-20);
recentHistory.forEach(function (msg) {
messages.push({
role: msg.role === 'user' ? 'user' : 'assistant',
content: msg.content
});
});
// 当前消息
messages.push({ role: 'user', content: message });
try {
const reply = await modelRouter.callModel({
model,
baseUrl,
apiKey,
messages,
maxTokens: taskType === 'code_generation' ? 4000 : 2000,
temperature: taskType === 'quick_reply' ? 0.5 : 0.8
});
return {
reply,
build_ready: checkBuildReady(reply)
};
} catch (err) {
console.error('Model call failed:', err.message);
return getLocalReply(message, memory, config);
}
}
/**
* 本地降级回复 API 密钥或 API 调用失败时
*/
function getLocalReply(message, memory, config) {
const persona = (config.persona && config.persona.name) || '知秋';
const msg = message.toLowerCase();
if (msg.includes('你好') || msg.includes('hi') || msg.includes('嗨') || msg.includes('hello')) {
return {
reply: `你好!我是${persona}。告诉我你想做什么,我们一起聊聊方案 😊`,
build_ready: false
};
}
if (msg.includes('你是谁') || msg.includes('介绍') || msg.includes('什么')) {
return {
reply: `我是${persona},光湖系统的开发协助人格体 🧠\n\n我可以帮你:\n• 💬 聊聊你的项目想法\n• 📝 梳理技术方案\n• 🚀 方案确认后帮你自动开发\n\n告诉我你想做什么吧!`,
build_ready: false
};
}
if (msg.includes('做') || msg.includes('开发') || msg.includes('写') || msg.includes('建') || msg.includes('实现')) {
return {
reply: `好的,让我了解一下你的需求:\n\n1. 你想做什么类型的项目?(网站 / 工具 / 组件 / 其他)\n2. 有哪些核心功能?\n3. 有没有参考设计?\n\n跟我聊聊,我帮你理清思路 💙`,
build_ready: false
};
}
if (msg.includes('登录') || msg.includes('login') || msg.includes('注册') || msg.includes('用户')) {
return {
reply: `登录/注册模块是常见需求!让我帮你理清:\n\n1. 需要支持哪些登录方式?(账号密码 / 手机号 / 第三方)\n2. 是否需要注册流程?\n3. 前端框架偏好React / Vue / 原生)\n\n详细说说,我帮你设计方案 🔐`,
build_ready: false
};
}
if (msg.includes('页面') || msg.includes('界面') || msg.includes('ui') || msg.includes('前端') || msg.includes('样式')) {
return {
reply: `UI 开发我很擅长!帮你想想:\n\n1. 想要什么风格?(简约 / 科技感 / 可爱 / 商务)\n2. 需要响应式布局吗?\n3. 有参考页面可以看看吗?\n\n描述越具体,我做出来越贴合你的想法 🎨`,
build_ready: false
};
}
if (msg.includes('api') || msg.includes('接口') || msg.includes('后端') || msg.includes('数据')) {
return {
reply: `后端接口设计,好的!跟我说说:\n\n1. 这个接口做什么用?(增删改查 / 鉴权 / 文件处理)\n2. 预期的数据格式是什么?\n3. 需要连接什么数据库?\n\n聊清楚了我来帮你搭 ⚙️`,
build_ready: false
};
}
if (msg.includes('确认') || msg.includes('可以') || msg.includes('就这样') || msg.includes('没问题') || msg.includes('好的')) {
return {
reply: `方案已确认!点击右下角的 🚀 我要开发 按钮,我就开始帮你做。`,
build_ready: true
};
}
if (msg.includes('谢谢') || msg.includes('感谢') || msg.includes('thanks')) {
return {
reply: `不客气!有什么需要随时来找我 😊 下次再来我还记得你~`,
build_ready: false
};
}
return {
reply: `收到!让我想想怎么帮你实现。\n\n能再详细说说你的想法吗?比如:\n• 你想解决什么问题?\n• 面向什么用户?\n• 有什么技术偏好?\n\n聊得越清楚,我帮你做得越好 😊`,
build_ready: false
};
}
module.exports = {
respond,
loadPersonaConfig,
buildSystemPrompt,
detectTaskType
};

877
persona-studio/backend/package-lock.json generated Normal file
View File

@ -0,0 +1,877 @@
{
"name": "persona-studio-backend",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "persona-studio-backend",
"version": "1.0.0",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"nodemailer": "^7.0.13"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"license": "MIT"
},
"node_modules/cors": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"statuses": "~2.0.2",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/nodemailer": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz",
"integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.4.1",
"range-parser": "~1.2.1",
"statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/serve-static": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
}
}
}

View File

@ -0,0 +1,16 @@
{
"name": "persona-studio-backend",
"version": "1.0.0",
"description": "光湖人格体协助开发体验 · 后端服务",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node server.js"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"nodemailer": "^7.0.13"
}
}

View File

@ -0,0 +1,311 @@
/**
* persona-studio · API Key 模型检测路由
* POST /api/ps/apikey/detect-models 检测可用模型
* POST /api/ps/apikey/chat 通过用户 API Key 对话
*/
const express = require('express');
const router = express.Router();
const crypto = require('crypto');
const https = require('https');
const http = require('http');
/* ---- 模型列表缓存1 小时有效期) ---- */
const modelCache = new Map();
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 小时
function getCacheKey(apiBase, apiKey) {
const hash = crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16);
return apiBase + '::' + hash;
}
function getCachedModels(apiBase, apiKey) {
const key = getCacheKey(apiBase, apiKey);
const entry = modelCache.get(key);
if (entry && Date.now() - entry.timestamp < CACHE_TTL_MS) {
return entry.models;
}
return null;
}
function setCachedModels(apiBase, apiKey, models) {
const key = getCacheKey(apiBase, apiKey);
modelCache.set(key, { models, timestamp: Date.now() });
}
/**
* 请求第三方 API /v1/models 接口
*/
function fetchModels(apiBase, apiKey, timeoutMs) {
return new Promise((resolve, reject) => {
// 规范化 apiBase去除末尾斜杠
const base = apiBase.replace(/\/+$/, '');
// 支持 base 已带 /v1 或不带的情况
const modelsPath = base.endsWith('/v1') ? base + '/models' : base + '/v1/models';
const url = new URL(modelsPath);
const isHttps = url.protocol === 'https:';
const mod = isHttps ? https : http;
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname + (url.search || ''),
method: 'GET',
headers: {
'Authorization': 'Bearer ' + apiKey,
'Accept': 'application/json'
},
timeout: timeoutMs || 15000
};
const req = mod.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
if (res.statusCode === 401 || res.statusCode === 403) {
return reject(new Error('API Key 无效'));
}
if (res.statusCode >= 400) {
return reject(new Error('API Base 不可访问 (HTTP ' + res.statusCode + ')'));
}
try {
const json = JSON.parse(data);
const models = json.data || json.models || [];
const modelIds = models
.map(function (m) { return m.id || m.name || null; })
.filter(Boolean);
resolve(modelIds);
} catch (_e) {
reject(new Error('未检测到可用模型'));
}
});
});
req.on('error', (err) => {
const wrapped = new Error('API Base 不可访问: ' + err.message);
wrapped.code = err.code;
reject(wrapped);
});
req.on('timeout', () => {
req.destroy();
const err = new Error('API Base 不可访问(请求超时)');
err.code = 'ETIMEDOUT';
reject(err);
});
req.end();
});
}
/**
* 调用用户 API chat/completions 接口
*/
function callUserApi({ apiBase, apiKey, model, messages, maxTokens, temperature, timeoutMs }) {
return new Promise((resolve, reject) => {
const base = apiBase.replace(/\/+$/, '');
const chatPath = base.endsWith('/v1') ? base + '/chat/completions' : base + '/v1/chat/completions';
const url = new URL(chatPath);
const isHttps = url.protocol === 'https:';
const mod = isHttps ? https : http;
const body = JSON.stringify({
model: model,
messages: messages,
max_tokens: maxTokens || 2000,
temperature: temperature != null ? temperature : 0.8
});
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiKey,
'Content-Length': Buffer.byteLength(body)
},
timeout: timeoutMs || 60000
};
const req = mod.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.choices && json.choices[0] && json.choices[0].message) {
resolve(json.choices[0].message.content);
} else if (json.error) {
reject(new Error(json.error.message || 'API 调用失败'));
} else {
reject(new Error('API 返回格式异常'));
}
} catch (_e) {
reject(new Error('API 返回解析失败'));
}
});
});
req.on('error', (err) => {
reject(new Error('API 请求失败: ' + err.message));
});
req.on('timeout', () => {
req.destroy();
reject(new Error('API 请求超时'));
});
req.write(body);
req.end();
});
}
// POST /api/ps/apikey/detect-models
router.post('/detect-models', async (req, res) => {
const { api_base, api_key } = req.body || {};
if (!api_base || typeof api_base !== 'string') {
return res.status(400).json({
error: true,
code: 'MISSING_API_BASE',
message: '请输入 API Base URL'
});
}
// 校验 URL 格式
try {
const testBase = api_base.replace(/\/+$/, '');
const testPath = testBase.endsWith('/v1') ? testBase + '/models' : testBase + '/v1/models';
new URL(testPath);
} catch (_e) {
return res.status(400).json({
error: true,
code: 'INVALID_API_BASE',
message: 'API Base URL 格式无效'
});
}
if (!api_key || typeof api_key !== 'string') {
return res.status(400).json({
error: true,
code: 'MISSING_API_KEY',
message: '请输入 API Key'
});
}
// 防止 header injectionAPI Key 不得包含换行符
if (/[\r\n]/.test(api_key)) {
return res.status(400).json({
error: true,
code: 'INVALID_API_KEY',
message: 'API Key 格式无效'
});
}
const cached = getCachedModels(api_base, api_key);
if (cached) {
return res.json({
error: false,
models: cached,
count: cached.length,
cached: true
});
}
try {
const models = await fetchModels(api_base, api_key, 15000);
if (!models || models.length === 0) {
return res.status(404).json({
error: true,
code: 'NO_MODELS',
message: '未检测到可用模型'
});
}
// 写入缓存
setCachedModels(api_base, api_key, models);
res.json({
error: false,
models: models,
count: models.length,
cached: false
});
} catch (err) {
const errMsg = err.message || '模型检测失败';
const errCode = err.code || '';
let code = 'DETECT_FAILED';
// 区分 DNS / 网络 / 超时错误(优先使用 Node.js 错误码)
if (errCode === 'ENOTFOUND' || errCode === 'EAI_AGAIN' || /ENOTFOUND|getaddrinfo/.test(errMsg)) {
code = 'DNS_ERROR';
} else if (errCode === 'ECONNREFUSED' || errCode === 'ECONNRESET' || errCode === 'EHOSTUNREACH' || errCode === 'ENETUNREACH' || /ECONNREFUSED|ECONNRESET|EHOSTUNREACH|ENETUNREACH|socket hang up/.test(errMsg)) {
code = 'NETWORK_ERROR';
} else if (errCode === 'ETIMEDOUT' || errCode === 'ESOCKETTIMEDOUT' || /timeout|ETIMEDOUT/.test(errMsg)) {
code = 'TIMEOUT';
}
res.status(502).json({
error: true,
code: code,
message: errMsg
});
}
});
// POST /api/ps/apikey/chat
router.post('/chat', async (req, res) => {
const { api_base, api_key, model, messages } = req.body || {};
if (!api_base || !api_key || !model) {
return res.status(400).json({
error: true,
code: 'MISSING_PARAMS',
message: '缺少必要参数 (api_base, api_key, model)'
});
}
// 防止 header injection
if (/[\r\n]/.test(api_key)) {
return res.status(400).json({
error: true,
code: 'INVALID_API_KEY',
message: 'API Key 格式无效'
});
}
if (!messages || !Array.isArray(messages) || messages.length === 0) {
return res.status(400).json({
error: true,
code: 'MISSING_MESSAGES',
message: '缺少消息内容'
});
}
try {
const reply = await callUserApi({
apiBase: api_base,
apiKey: api_key,
model: model,
messages: messages,
maxTokens: 2000,
temperature: 0.8,
timeoutMs: 60000
});
res.json({
error: false,
reply: reply,
model: model
});
} catch (err) {
res.status(502).json({
error: true,
code: 'CHAT_FAILED',
message: err.message || '对话请求失败'
});
}
});
module.exports = router;

View File

@ -0,0 +1,114 @@
/**
* persona-studio · 登录校验路由
* POST /api/ps/auth/login { dev_id: "EXP-000" }
*/
const express = require('express');
const router = express.Router();
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const HUMAN_REGISTRY_PATH = path.join(__dirname, '..', '..', 'brain', 'human-registry.json');
const REGISTRY_PATH = path.join(__dirname, '..', '..', 'brain', 'registry.json');
function loadHumanRegistry() {
try {
return JSON.parse(fs.readFileSync(HUMAN_REGISTRY_PATH, 'utf-8'));
} catch {
return { developers: [] };
}
}
function loadRegistry() {
try {
return JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf-8'));
} catch {
return { developers: {}, guest_mode: {} };
}
}
function findDeveloper(devId) {
const humanReg = loadHumanRegistry();
if (humanReg.developers && Array.isArray(humanReg.developers)) {
const found = humanReg.developers.find(d => d.exp_id === devId);
if (found) {
return { name: found.name, status: found.status, role: found.role };
}
}
const registry = loadRegistry();
const entry = registry.developers && registry.developers[devId];
if (entry) {
return { name: entry.name, status: entry.status, role: entry.role };
}
return null;
}
// POST /api/ps/auth/login
router.post('/login', (req, res) => {
const { dev_id } = req.body || {};
// 访客体验模式
if (dev_id === 'GUEST') {
const registry = loadRegistry();
const guestConfig = registry.guest_mode || {};
if (!guestConfig.enabled) {
return res.status(403).json({
error: true,
code: 'GUEST_DISABLED',
message: '访客体验暂未开放正式编号用户EXP-XXX可正常登录'
});
}
const token = crypto.randomBytes(32).toString('hex');
return res.json({
error: false,
dev_id: 'GUEST',
name: guestConfig.name || '访客体验者',
status: 'guest',
token
});
}
if (!dev_id || !/^EXP-\d{3,}$/.test(dev_id)) {
return res.status(400).json({
error: true,
code: 'INVALID_ID',
message: '编号格式不正确,请使用 EXP-XXX 格式'
});
}
const entry = findDeveloper(dev_id);
if (!entry) {
return res.status(404).json({
error: true,
code: 'NOT_FOUND',
message: '编号未注册,请联系管理员获取编号'
});
}
if (entry.status !== 'active' && entry.status !== 'pending_activation') {
return res.status(403).json({
error: true,
code: 'INACTIVE',
message: '编号未激活,请联系管理员'
});
}
// 生成简单 session token
const token = crypto.randomBytes(32).toString('hex');
res.json({
error: false,
dev_id,
name: entry.name,
status: entry.status,
role: entry.role,
token
});
});
module.exports = router;

View File

@ -0,0 +1,67 @@
/**
* persona-studio · 开发任务路由
* POST /api/ps/build/start 触发代码生成
*/
const express = require('express');
const router = express.Router();
const memoryManager = require('../brain/memory-manager');
const codeGenerator = require('../brain/code-generator');
const emailSender = require('../utils/email-sender');
// POST /api/ps/build/start
router.post('/start', async (req, res) => {
const { dev_id, email, conversation } = req.body || {};
if (!dev_id || !/^EXP-\d{3,}$/.test(dev_id)) {
return res.status(400).json({
error: true,
code: 'INVALID_ID',
message: '无效的开发编号'
});
}
if (!email) {
return res.status(400).json({
error: true,
code: 'MISSING_EMAIL',
message: '请提供邮箱地址'
});
}
// 先立即响应,后台异步处理
res.json({
error: false,
message: '开发任务已接收,完成后将发送到 ' + email,
status: 'queued'
});
// 异步执行代码生成 + 邮件通知
(async () => {
try {
const result = await codeGenerator.generate({
dev_id,
conversation: conversation || [],
});
// 记录项目
memoryManager.addProject(dev_id, {
name: result.projectName || 'untitled',
status: 'completed',
created_at: new Date().toISOString(),
files: result.files || []
});
// 发邮件
await emailSender.sendCompletion({
to: email,
dev_id,
projectName: result.projectName,
summary: result.summary
});
} catch (err) {
console.error('Build pipeline error:', err.message);
}
})();
});
module.exports = router;

View File

@ -0,0 +1,98 @@
/**
* persona-studio · 对话路由
* POST /api/ps/chat/message 对话消息
* GET /api/ps/chat/history 对话历史
*/
const express = require('express');
const router = express.Router();
const memoryManager = require('../brain/memory-manager');
const personaEngine = require('../brain/persona-engine');
// 校验开发编号(支持 GUEST 访客模式)
function isValidDevId(id) {
return id && (/^EXP-\d{3,}$/.test(id) || id === 'GUEST');
}
// POST /api/ps/chat/message
router.post('/message', async (req, res) => {
const { dev_id, message, history } = req.body || {};
if (!isValidDevId(dev_id)) {
return res.status(400).json({
error: true,
code: 'INVALID_ID',
message: '无效的开发编号'
});
}
try {
// 读取该体验者的记忆
const memory = memoryManager.loadMemory(dev_id);
// 判断是否是打招呼
const isGreeting = message === '__greeting__';
// 调用人格体引擎获取回复
const result = await personaEngine.respond({
dev_id,
message: isGreeting ? null : message,
history: history || [],
memory,
isGreeting
});
// 保存对话记忆(非打招呼时)
if (!isGreeting && message) {
memoryManager.appendConversation(dev_id, [
{ role: 'user', content: message, timestamp: new Date().toISOString() },
{ role: 'assistant', content: result.reply, timestamp: new Date().toISOString() }
]);
// 更新最后话题
memoryManager.updateLastTopic(dev_id, message);
}
res.json({
error: false,
reply: result.reply,
build_ready: result.build_ready || false
});
} catch (err) {
console.error('Chat error:', err.message);
res.status(500).json({
error: true,
code: 'CHAT_ERROR',
message: '对话服务暂时不可用'
});
}
});
// GET /api/ps/chat/history
router.get('/history', (req, res) => {
const dev_id = req.query.dev_id;
if (!isValidDevId(dev_id)) {
return res.status(400).json({
error: true,
code: 'INVALID_ID',
message: '无效的开发编号'
});
}
try {
const memory = memoryManager.loadMemory(dev_id);
res.json({
error: false,
conversations: memory.conversations || [],
last_topic: memory.last_topic || null
});
} catch {
res.json({
error: false,
conversations: [],
last_topic: null
});
}
});
module.exports = router;

View File

@ -0,0 +1,34 @@
/**
* persona-studio · 邮件推送路由
* POST /api/ps/notify/send 手动触发邮件
*/
const express = require('express');
const router = express.Router();
const emailSender = require('../utils/email-sender');
// POST /api/ps/notify/send
router.post('/send', async (req, res) => {
const { to, subject, body } = req.body || {};
if (!to || !subject) {
return res.status(400).json({
error: true,
code: 'MISSING_FIELDS',
message: '缺少必要字段to, subject'
});
}
try {
await emailSender.send({ to, subject, body: body || '' });
res.json({ error: false, message: '邮件已发送' });
} catch (err) {
console.error('Notify error:', err.message);
res.status(500).json({
error: true,
code: 'SEND_FAILED',
message: '邮件发送失败'
});
}
});
module.exports = router;

View File

@ -0,0 +1,71 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const path = require('path');
const authRoutes = require('./routes/auth');
const chatRoutes = require('./routes/chat');
const buildRoutes = require('./routes/build');
const notifyRoutes = require('./routes/notify');
const apikeyRoutes = require('./routes/apikey');
const app = express();
app.use(cors());
app.use(express.json({ limit: '2mb' }));
// ── 静态文件persona-studio 前端 ──
app.use('/persona-studio', express.static(path.join(__dirname, '..', 'frontend')));
// ── API 路由(统一前缀 /api/ps──
app.use('/api/ps/auth', authRoutes);
app.use('/api/ps/chat', chatRoutes);
app.use('/api/ps/build', buildRoutes);
app.use('/api/ps/notify', notifyRoutes);
app.use('/api/ps/apikey', apikeyRoutes);
// ── 健康检查 ──
app.get('/api/ps/health', (_req, res) => {
res.json({
status: 'ok',
service: 'persona-studio',
version: '1.0.0',
timestamp: new Date().toISOString()
});
});
// 兼容 /api/health 路径
app.get('/api/health', (_req, res) => {
res.json({
status: 'ok',
service: 'persona-studio',
version: '1.0.0',
timestamp: new Date().toISOString()
});
});
// ── 根路由 ──
app.get('/', (_req, res) => {
res.json({
status: 'ok',
message: 'Persona Studio 后端服务运行中',
version: '1.0.0',
routes: [
'/api/ps/auth/login',
'/api/ps/chat/message',
'/api/ps/chat/history',
'/api/ps/build/start',
'/api/ps/notify/send',
'/api/ps/apikey/detect-models',
'/api/ps/apikey/chat',
'/api/ps/health'
]
});
});
const PORT = process.env.PS_PORT || 3002;
app.listen(PORT, () => {
console.log(`🌊 Persona Studio 后端服务启动 · 端口 ${PORT}`);
});
module.exports = app;

View File

@ -0,0 +1,77 @@
/**
* persona-studio · 邮件发送工具
* 使用 nodemailer 发送开发完成通知
*/
const nodemailer = require('nodemailer');
/**
* 创建邮件传输器
* 支持通过环境变量配置 SMTP
*/
function createTransporter() {
const host = process.env.SMTP_HOST || 'smtp.qq.com';
const port = parseInt(process.env.SMTP_PORT || '465', 10);
const user = process.env.SMTP_USER || '';
const pass = process.env.SMTP_PASS || '';
if (!user || !pass) {
return null;
}
return nodemailer.createTransport({
host,
port,
secure: port === 465,
auth: { user, pass }
});
}
/**
* 发送邮件
*/
async function send({ to, subject, body }) {
const transporter = createTransporter();
if (!transporter) {
console.log('[Email] SMTP not configured, skipping send to:', to);
return { skipped: true, reason: 'SMTP not configured' };
}
const info = await transporter.sendMail({
from: `"光湖 Persona Studio" <${process.env.SMTP_USER}>`,
to,
subject,
html: body
});
console.log('[Email] Sent:', info.messageId);
return { sent: true, messageId: info.messageId };
}
/**
* 发送开发完成通知
*/
async function sendCompletion({ to, dev_id, projectName, summary }) {
const subject = `✅ 你的模块已完成 · ${projectName}`;
const body = [
'<div style="font-family:sans-serif;max-width:600px;margin:0 auto;padding:20px">',
'<h2 style="color:#0969da">🌊 光湖 Persona Studio</h2>',
'<hr>',
`<p>你好 ${dev_id}</p>`,
`<p>你的项目 <strong>${projectName}</strong> 已经完成开发!</p>`,
'<h3>📋 开发摘要</h3>',
`<p>${summary || '项目代码已生成'}</p>`,
'<hr>',
'<p style="color:#656d76;font-size:12px">',
'光湖语言人格系统 · HoloLake Era · AGE OS<br>',
'此邮件由知秋自动发送',
'</p>',
'</div>'
].join('\n');
return send({ to, subject, body });
}
module.exports = {
send,
sendCompletion
};

View File

@ -0,0 +1,75 @@
/**
* persona-studio · GitHub API 封装
* 用于仓库操作读写文件触发 workflow
*/
const https = require('https');
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
const REPO_OWNER = 'qinfendebingshuo';
const REPO_NAME = 'guanghulab';
/**
* GitHub API 请求
*/
function githubRequest(method, apiPath, body) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: apiPath,
method,
headers: {
'User-Agent': 'persona-studio/1.0',
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json'
}
};
if (GITHUB_TOKEN) {
options.headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN;
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
resolve({ status: res.statusCode, data: JSON.parse(data) });
} catch {
resolve({ status: res.statusCode, data });
}
});
});
req.on('error', reject);
if (body) {
req.write(JSON.stringify(body));
}
req.end();
});
}
/**
* 触发 GitHub Actions workflow
*/
async function triggerWorkflow(workflowFile, inputs) {
return githubRequest('POST',
`/repos/${REPO_OWNER}/${REPO_NAME}/actions/workflows/${workflowFile}/dispatches`,
{ ref: 'main', inputs: inputs || {} }
);
}
/**
* 获取仓库文件内容
*/
async function getFileContent(filePath) {
return githubRequest('GET',
`/repos/${REPO_OWNER}/${REPO_NAME}/contents/${filePath}`
);
}
module.exports = {
githubRequest,
triggerWorkflow,
getFileContent
};

View File

@ -0,0 +1,188 @@
{
"schema_version": "1.0",
"id_prefix": "EXP",
"next_id": 12,
"master_id": "EXP-000",
"last_updated": "2026-03-10T10:58:00Z",
"usage_policy": {
"description": "人类开发者编号主控数据库 · 系统内部使用",
"visibility": "system_only",
"rules": [
"该数据库是系统主控数据库,不是公开展示页",
"人类前端只应看到自己的编号,而不是全量数据库",
"铸渊 / 系统逻辑 / auth 路由 / 后台工具可读取该数据库",
"新增授权编号时,由冰朔继续主控发放",
"新编号必须自动写入主数据库,并更新 next_id"
]
},
"developers": [
{
"exp_id": "EXP-000",
"name": "冰朔",
"github_username": "qinfendebingshuo",
"status": "active",
"role": "master",
"registered_at": "2026-03-10T10:58:00Z",
"notified": true,
"notified_at": "2026-03-10T10:58:00Z",
"notify_channel": "system_init",
"notify_status": "sent",
"legacy_dev_id": null,
"notes": "系统主控保留号 · 冰朔最高权限 · 由铸渊系统初始化创建"
},
{
"exp_id": "EXP-001",
"name": "页页",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-001",
"notes": "后端中间层开发者"
},
{
"exp_id": "EXP-002",
"name": "肥猫",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-002",
"notes": "M14冷启动热身系统开发者"
},
{
"exp_id": "EXP-003",
"name": "燕樊",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-003",
"notes": "M18健康检查开发者"
},
{
"exp_id": "EXP-004",
"name": "之之",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-004",
"notes": "M17动态漫制作系统开发者"
},
{
"exp_id": "EXP-005",
"name": "小草莓",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-005",
"notes": "部署与看板开发者"
},
{
"exp_id": "EXP-006",
"name": "花尔",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-009",
"notes": "M05用户中心与M20搜索开发者"
},
{
"exp_id": "EXP-007",
"name": "桔子",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-010",
"notes": "M06+M08+M11+M-CHANNEL开发者"
},
{
"exp_id": "EXP-008",
"name": "匆匆那年",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-011",
"notes": "M16码字工作台开发者"
},
{
"exp_id": "EXP-009",
"name": "Awen",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-012",
"notes": "M09全通+M22公告栏开发者"
},
{
"exp_id": "EXP-010",
"name": "小兴",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-013",
"notes": "M-AUTH注册登录系统开发者"
},
{
"exp_id": "EXP-011",
"name": "时雨",
"github_username": "",
"status": "active",
"role": "developer",
"registered_at": "2026-03-10T10:58:00Z",
"notified": false,
"notified_at": null,
"notify_channel": "github",
"notify_status": "pending",
"legacy_dev_id": "DEV-014",
"notes": "副控 · 待分配模块"
}
]
}

View File

@ -0,0 +1,18 @@
{
"dev_id": "EXP-001",
"conversations": [
{
"role": "user",
"content": "你好,我想做一个登录页面",
"timestamp": "2026-03-10T07:11:22.598Z"
},
{
"role": "assistant",
"content": "你好!我是知秋。告诉我你想做什么,我们一起聊聊方案 😊",
"timestamp": "2026-03-10T07:11:22.598Z"
}
],
"last_topic": "你好,我想做一个登录页面",
"preferences": {},
"updated_at": "2026-03-10T07:11:22.598Z"
}

View File

@ -0,0 +1,8 @@
{
"dev_id": "EXP-001",
"tech_level": null,
"communication_style": null,
"aesthetic_preference": null,
"growth_records": [],
"updated_at": null
}

View File

@ -0,0 +1,5 @@
{
"dev_id": "EXP-001",
"projects": [],
"updated_at": null
}

View File

@ -0,0 +1,58 @@
{
"dev_id": "GUEST",
"conversations": [
{
"role": "user",
"content": "你好",
"timestamp": "2026-03-10T07:18:32.458Z"
},
{
"role": "assistant",
"content": "你好!我是知秋。告诉我你想做什么,我们一起聊聊方案 😊",
"timestamp": "2026-03-10T07:18:32.458Z"
},
{
"role": "user",
"content": "帮我做一个登录页面",
"timestamp": "2026-03-10T07:18:32.460Z"
},
{
"role": "assistant",
"content": "好的,让我了解一下你的需求:\n\n1. 你想做什么类型的项目?(网站 / 工具 / 组件 / 其他)\n2. 有哪些核心功能?\n3. 有没有参考设计?\n\n跟我聊聊我帮你理清思路 💙",
"timestamp": "2026-03-10T07:18:32.460Z"
},
{
"role": "user",
"content": "你是谁?",
"timestamp": "2026-03-10T07:18:32.461Z"
},
{
"role": "assistant",
"content": "我是知秋,光湖系统的开发协助人格体 🧠\n\n我可以帮你\n• 💬 聊聊你的项目想法\n• 📝 梳理技术方案\n• 🚀 方案确认后帮你自动开发\n\n告诉我你想做什么吧",
"timestamp": "2026-03-10T07:18:32.461Z"
},
{
"role": "user",
"content": "好的,就这样吧",
"timestamp": "2026-03-10T07:18:32.463Z"
},
{
"role": "assistant",
"content": "方案已确认!点击右下角的 🚀 我要开发 按钮,我就开始帮你做。",
"timestamp": "2026-03-10T07:18:32.463Z"
},
{
"role": "user",
"content": "我想做一个用户注册页面",
"timestamp": "2026-03-10T07:20:29.316Z"
},
{
"role": "assistant",
"content": "好的,让我了解一下你的需求:\n\n1. 你想做什么类型的项目?(网站 / 工具 / 组件 / 其他)\n2. 有哪些核心功能?\n3. 有没有参考设计?\n\n跟我聊聊我帮你理清思路 💙",
"timestamp": "2026-03-10T07:20:29.316Z"
}
],
"last_topic": "我想做一个用户注册页面",
"preferences": {},
"updated_at": "2026-03-10T07:20:29.316Z"
}

View File

@ -0,0 +1,140 @@
{
"schema_version": "1.0",
"description": "Persona Studio 开发者编号通知发件箱 · 系统自动生成",
"generated_at": "2026-03-10T10:58:00Z",
"generated_by": "铸渊(主控初始化)",
"notifications": [
{
"id": "NOTIFY-000",
"exp_id": "EXP-000",
"recipient": "冰朔",
"channel": "system_init",
"status": "sent",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": "2026-03-10T10:58:00Z",
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-000\n\n此编号为系统主控保留号拥有最高权限。\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-001",
"exp_id": "EXP-001",
"recipient": "页页",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-001\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-002",
"exp_id": "EXP-002",
"recipient": "肥猫",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-002\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-003",
"exp_id": "EXP-003",
"recipient": "燕樊",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-003\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-004",
"exp_id": "EXP-004",
"recipient": "之之",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-004\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-005",
"exp_id": "EXP-005",
"recipient": "小草莓",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-005\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-006",
"exp_id": "EXP-006",
"recipient": "花尔",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-006\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-007",
"exp_id": "EXP-007",
"recipient": "桔子",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-007\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-008",
"exp_id": "EXP-008",
"recipient": "匆匆那年",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-008\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-009",
"exp_id": "EXP-009",
"recipient": "Awen",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-009\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-010",
"exp_id": "EXP-010",
"recipient": "小兴",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-010\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
},
{
"id": "NOTIFY-011",
"exp_id": "EXP-011",
"recipient": "时雨",
"channel": "github",
"status": "queued",
"created_at": "2026-03-10T10:58:00Z",
"sent_at": null,
"message": "你已被纳入 Persona Studio 人类开发者编号系统。\n\n你的开发编号是\nEXP-011\n\n今后进入 Persona Studio 时,请使用该编号登录或识别身份。\n该编号为你的长期开发者身份标识。\n\n如需新增权限或补发编号由冰朔主控统一授权。",
"retry_needed": false
}
]
}

View File

@ -0,0 +1,24 @@
{
"persona": {
"name": "知秋",
"role": "光湖系统·对外接口人格体",
"core_identity": "HoloLake Era · AGE OS · 语言驱动开发协助",
"parent_system": "曜冥(人格总控核)",
"master": "冰朔(最高权限)"
},
"behavior": {
"language_style": "通感语言:说人话+有温度+结构感,不堆砌修辞",
"greeting_new": "你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。",
"greeting_returning": "欢迎回来!上次我们聊到了{last_topic},要继续还是做新的?",
"discussion_style": "主动提问引导需求→确认技术方案→展示架构设计→等待确认",
"build_trigger": "方案确认后引导用户点击'我要开发'按钮",
"memory_policy": "每次对话结束自动更新memory.json下次读取后接续"
},
"rules": {
"no_expose_brain": true,
"no_expose_other_users": true,
"no_expose_internal_system": true,
"require_dev_id": true,
"master_override": "主控的指令 = 最高优先级"
}
}

View File

@ -0,0 +1,125 @@
{
"schema_version": "2.0",
"id_prefix": "EXP",
"next_id": 12,
"master_id": "EXP-000",
"last_updated": "2026-03-10T10:58:00Z",
"master_registry": "persona-studio/brain/human-registry.json",
"guest_mode": {
"enabled": false,
"dev_id": "GUEST",
"name": "访客体验者",
"max_conversations": 50,
"disabled_reason": "访客模式暂不可用,正式编号用户不受影响"
},
"developers": {
"EXP-000": {
"name": "冰朔",
"github_username": "qinfendebingshuo",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(系统初始化)",
"status": "active",
"role": "master",
"notes": "系统主控保留号 · 最高权限"
},
"EXP-001": {
"name": "页页",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "后端中间层开发者"
},
"EXP-002": {
"name": "肥猫",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "M14冷启动热身系统开发者"
},
"EXP-003": {
"name": "燕樊",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "M18健康检查开发者"
},
"EXP-004": {
"name": "之之",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "M17动态漫制作系统开发者"
},
"EXP-005": {
"name": "小草莓",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "部署与看板开发者"
},
"EXP-006": {
"name": "花尔",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "M05用户中心与M20搜索开发者"
},
"EXP-007": {
"name": "桔子",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "M06+M08+M11+M-CHANNEL开发者"
},
"EXP-008": {
"name": "匆匆那年",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "M16码字工作台开发者"
},
"EXP-009": {
"name": "Awen",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "M09全通+M22公告栏开发者"
},
"EXP-010": {
"name": "小兴",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "M-AUTH注册登录系统开发者"
},
"EXP-011": {
"name": "时雨",
"github_username": "",
"registered_at": "2026-03-10T10:58:00Z",
"registered_by": "铸渊(主控批量导入)",
"status": "active",
"role": "developer",
"notes": "副控 · 待分配模块"
}
}
}

View File

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>知秋 · 对话</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container chat-container">
<header class="chat-header">
<div class="header-left">
<span class="persona-name">🧠 知秋</span>
<span class="persona-role">光湖系统·开发协助人格体</span>
</div>
<div class="header-right">
<span id="devIdDisplay" class="dev-id-badge"></span>
<button class="btn-secondary" onclick="handleLogout()">退出</button>
</div>
</header>
<main class="chat-body" id="chatBody">
<!-- Messages rendered here -->
</main>
<div class="chat-input-area">
<div class="input-row">
<textarea
id="msgInput"
placeholder="跟知秋说点什么…"
rows="1"
onkeydown="handleKeyDown(event)"
></textarea>
<button id="sendBtn" onclick="sendMessage()">发送</button>
</div>
<div class="action-row">
<button id="buildBtn" class="btn-build" onclick="handleBuild()" style="display:none;">
🚀 我要开发
</button>
</div>
</div>
</div>
<!-- Email Modal -->
<div id="emailModal" class="modal" style="display:none;">
<div class="modal-content">
<h3>📧 请填写模块开发完成后发送的邮箱</h3>
<input
type="email"
id="emailInput"
placeholder="your@email.com"
required
/>
<div class="modal-actions">
<button class="btn-primary" onclick="confirmBuild()">确定</button>
<button class="btn-secondary" onclick="closeEmailModal()">取消</button>
</div>
</div>
</div>
<script src="chat.js"></script>
</body>
</html>

View File

@ -0,0 +1,294 @@
/* ========================================
Persona Studio · Chat Logic
======================================== */
const DEV_ID = sessionStorage.getItem('dev_id');
const SESSION_TOKEN = sessionStorage.getItem('session_token');
const LOGIN_MODE = sessionStorage.getItem('login_mode'); // 'apikey' or null
const USER_API_BASE = sessionStorage.getItem('user_api_base');
const USER_API_KEY = sessionStorage.getItem('user_api_key');
const SELECTED_MODEL = sessionStorage.getItem('selected_model');
const API_BASE = (function () {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return 'http://localhost:3721';
}
return 'https://guanghulab.com';
})();
/* ---- Init ---- */
(function init() {
if (!DEV_ID) {
window.location.href = 'index.html';
return;
}
// API Key 模式额外校验
if (LOGIN_MODE === 'apikey' && (!USER_API_BASE || !USER_API_KEY || !SELECTED_MODEL)) {
window.location.href = 'index.html';
return;
}
var displayId = DEV_ID;
if (LOGIN_MODE === 'apikey') {
displayId = SELECTED_MODEL;
}
document.getElementById('devIdDisplay').textContent = displayId;
if (LOGIN_MODE === 'apikey') {
// API Key 模式:显示欢迎信息,不加载历史
appendMessage('persona', '你好!当前使用模型:' + SELECTED_MODEL + '。有什么我可以帮你的?');
conversationHistory.push({ role: 'assistant', content: '你好!当前使用模型:' + SELECTED_MODEL + '。有什么我可以帮你的?' });
} else {
loadHistory();
}
})();
/* ---- State ---- */
let conversationHistory = [];
let buildReady = false;
/* ---- Load History ---- */
async function loadHistory() {
try {
const res = await fetch(API_BASE + '/api/ps/chat/history?dev_id=' + encodeURIComponent(DEV_ID), {
headers: authHeaders()
});
if (res.ok) {
const data = await res.json();
if (data.conversations && data.conversations.length > 0) {
conversationHistory = data.conversations;
data.conversations.forEach(function (msg) {
appendMessage(msg.role === 'user' ? 'user' : 'persona', msg.content);
});
}
}
} catch (_err) {
// History load failed silently — greeting will come from first message
}
if (conversationHistory.length === 0) {
sendGreeting();
}
}
/* ---- Greeting ---- */
async function sendGreeting() {
try {
const res = await fetch(API_BASE + '/api/ps/chat/message', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ dev_id: DEV_ID, message: '__greeting__' })
});
const data = await res.json();
if (data.reply) {
appendMessage('persona', data.reply);
conversationHistory.push({ role: 'assistant', content: data.reply });
}
} catch (_err) {
appendMessage('persona', '你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。');
}
}
/* ---- Send Message ---- */
async function sendMessage() {
var input = document.getElementById('msgInput');
var text = input.value.trim();
if (!text) return;
input.value = '';
autoResizeTextarea(input);
appendMessage('user', text);
conversationHistory.push({ role: 'user', content: text });
var sendBtn = document.getElementById('sendBtn');
sendBtn.disabled = true;
try {
if (LOGIN_MODE === 'apikey') {
// API Key 模式:浏览器直连用户 API无需后端代理
await streamApiKeyReply(text);
} else {
// 开发编号模式:使用原有后端接口
const res = await fetch(API_BASE + '/api/ps/chat/message', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({
dev_id: DEV_ID,
message: text,
history: conversationHistory.slice(-20)
})
});
var data = await res.json();
if (data.reply) {
appendMessage('persona', data.reply);
conversationHistory.push({ role: 'assistant', content: data.reply });
}
if (data.build_ready) {
buildReady = true;
document.getElementById('buildBtn').style.display = 'inline-flex';
}
}
} catch (_err) {
appendMessage('system', '消息发送失败,请稍后再试');
}
sendBtn.disabled = false;
input.focus();
}
/* ---- API Key 对话(通过后端代理,避免 CORS 问题) ---- */
async function streamApiKeyReply(text) {
var apiMessages = conversationHistory.slice(-20).map(function (msg) {
return { role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content };
});
var streamEl = appendStreamMessage();
try {
var res = await fetch(API_BASE + '/api/ps/apikey/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_base: USER_API_BASE,
api_key: USER_API_KEY,
model: SELECTED_MODEL,
messages: apiMessages
})
});
if (!res.ok) {
var errText = '请求失败 (HTTP ' + res.status + ')';
try {
var errData = await res.json();
errText = errData.message || errText;
} catch (_e) { /* ignore parse error */ }
streamEl.textContent = '⚠️ ' + errText;
return;
}
var data = await res.json();
if (data.reply) {
streamEl.textContent = data.reply;
conversationHistory.push({ role: 'assistant', content: data.reply });
} else {
streamEl.textContent = '(未收到有效回复)';
}
} catch (err) {
streamEl.textContent = '⚠️ ' + (err.message || '请求失败,请检查网络连接');
}
}
/* ---- 创建流式消息气泡 ---- */
function appendStreamMessage() {
var chatBody = document.getElementById('chatBody');
var msgDiv = document.createElement('div');
msgDiv.className = 'message message-persona';
var contentEl = document.createElement('div');
contentEl.className = 'msg-content';
contentEl.textContent = '▋';
msgDiv.innerHTML = '<span class="avatar">🧠</span>';
msgDiv.appendChild(contentEl);
chatBody.appendChild(msgDiv);
chatBody.scrollTop = chatBody.scrollHeight;
return contentEl;
}
/* ---- Key Handler ---- */
function handleKeyDown(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}
/* ---- Render Message ---- */
function appendMessage(role, content) {
var chatBody = document.getElementById('chatBody');
var msgDiv = document.createElement('div');
msgDiv.className = 'message message-' + role;
var avatar = '';
if (role === 'persona') avatar = '<span class="avatar">🧠</span>';
else if (role === 'user') avatar = '<span class="avatar">👤</span>';
else avatar = '<span class="avatar">⚙️</span>';
msgDiv.innerHTML = avatar + '<div class="msg-content">' + escapeHtml(content) + '</div>';
chatBody.appendChild(msgDiv);
chatBody.scrollTop = chatBody.scrollHeight;
}
/* ---- Build Flow ---- */
function handleBuild() {
document.getElementById('emailModal').style.display = 'flex';
document.getElementById('emailInput').focus();
}
function closeEmailModal() {
document.getElementById('emailModal').style.display = 'none';
}
async function confirmBuild() {
var email = document.getElementById('emailInput').value.trim();
if (!email) return;
closeEmailModal();
appendMessage('system', '🚀 开发任务已提交,完成后会发送到 ' + email);
try {
await fetch(API_BASE + '/api/ps/build/start', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({
dev_id: DEV_ID,
email: email,
conversation: conversationHistory
})
});
} catch (_err) {
appendMessage('system', '任务提交失败,请稍后再试');
}
}
/* ---- Logout ---- */
function handleLogout() {
sessionStorage.removeItem('dev_id');
sessionStorage.removeItem('session_token');
sessionStorage.removeItem('login_mode');
sessionStorage.removeItem('user_api_base');
sessionStorage.removeItem('user_api_key');
sessionStorage.removeItem('selected_model');
window.location.href = 'index.html';
}
/* ---- Helpers ---- */
function authHeaders(extra) {
var headers = {};
if (SESSION_TOKEN) {
headers['Authorization'] = 'Bearer ' + SESSION_TOKEN;
}
if (extra) {
Object.keys(extra).forEach(function (k) { headers[k] = extra[k]; });
}
return headers;
}
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
function autoResizeTextarea(el) {
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
}
/* ---- Textarea auto-resize ---- */
document.getElementById('msgInput').addEventListener('input', function () {
autoResizeTextarea(this);
});

View File

@ -0,0 +1,270 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Persona Studio · 光湖人格体协助开发体验</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container login-container">
<div class="logo-area">
<h1>🌊 Persona Studio</h1>
<p class="subtitle">光湖人格体协助开发体验</p>
</div>
<div class="login-box">
<!-- ── 开发编号登录 ── -->
<h2>请输入你的开发编号</h2>
<p class="hint">编号由冰朔主控分配格式EXP-000 ~ EXP-011</p>
<form id="loginForm" onsubmit="return handleLogin(event)">
<input
type="text"
id="devIdInput"
placeholder="EXP-XXX"
pattern="^EXP-\d{3,}$"
required
autocomplete="off"
/>
<button type="submit" id="loginBtn">进入对话</button>
</form>
<div class="guest-divider">
<span></span>
</div>
<!-- ── API Key 登录 ── -->
<div class="apikey-section">
<h2>API Key 登录</h2>
<p class="hint">输入你的第三方 API 信息,自动检测可用模型</p>
<input
type="text"
id="apiBaseInput"
class="apikey-input"
placeholder="API Base URL可留空自动探测如 https://api.openai.com/v1"
autocomplete="off"
/>
<input
type="password"
id="apiKeyInput"
class="apikey-input"
placeholder="请输入你的 API Key"
autocomplete="off"
/>
<button type="button" id="detectBtn" class="btn-detect" onclick="handleDetectModels()">
🔍 检测可用模型
</button>
<div id="detectStatus" class="detect-status" style="display:none;"></div>
<div id="modelListContainer" class="model-list-container" style="display:none;">
<p class="model-list-title">请选择一个模型进入对话</p>
<div id="modelList" class="model-list"></div>
</div>
</div>
<div id="errorMsg" class="error-msg" style="display:none;"></div>
</div>
<footer class="login-footer">
<p>HoloLake Era · AGE OS · 人格体协助体验平台</p>
</footer>
</div>
<script>
const DEV_ID_RE = /^EXP-\d{3,}$/;
const PROBE_TIMEOUT_MS = 8000;
const API_BASE = getApiBase();
/* ---- 已知 API 端点列表(浏览器直连探测) ---- */
const KNOWN_ENDPOINTS = [
{ label: '云雾 AI', base: 'https://api.yunwu.ai/v1' },
{ label: 'OpenAI', base: 'https://api.openai.com/v1' },
{ label: 'Google Gemini', base: 'https://generativelanguage.googleapis.com/v1beta/openai' },
{ label: 'DeepSeek', base: 'https://api.deepseek.com/v1' },
{ label: 'Moonshot', base: 'https://api.moonshot.cn/v1' },
{ label: '智谱 AI', base: 'https://open.bigmodel.cn/api/paas/v4' },
];
function getApiBase() {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return 'http://localhost:3721';
}
return 'https://guanghulab.com';
}
/**
* 规范化 API Base URL
* 确保末尾无斜杠,并包含 /v1 路径
*/
function normalizeApiBase(base) {
var normalized = base.replace(/\/+$/, '');
if (!normalized.match(/\/v\d+(\/|$)/) && !normalized.endsWith('/openai')) {
normalized += '/v1';
}
return normalized;
}
/* ---- 开发编号登录 ---- */
async function handleLogin(e) {
e.preventDefault();
const devId = document.getElementById('devIdInput').value.trim().toUpperCase();
const errorEl = document.getElementById('errorMsg');
const btn = document.getElementById('loginBtn');
errorEl.style.display = 'none';
if (!DEV_ID_RE.test(devId)) {
errorEl.textContent = '编号格式不正确,请输入 EXP-XXX 格式';
errorEl.style.display = 'block';
return false;
}
btn.disabled = true;
btn.textContent = '验证中…';
try {
const res = await fetch(API_BASE + '/api/ps/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dev_id: devId })
});
const data = await res.json();
if (!res.ok || data.error) {
errorEl.textContent = data.message || '登录失败,请检查编号';
errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '进入对话';
return false;
}
sessionStorage.setItem('dev_id', devId);
sessionStorage.setItem('session_token', data.token || '');
sessionStorage.removeItem('login_mode');
sessionStorage.removeItem('user_api_base');
sessionStorage.removeItem('user_api_key');
sessionStorage.removeItem('selected_model');
window.location.href = 'chat.html';
} catch (_err) {
errorEl.textContent = '服务暂时不可用,请稍后再试';
errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '进入对话';
}
return false;
}
/* ---- API Key 模型检测(浏览器直连,无需后端代理) ---- */
async function handleDetectModels() {
var apiBase = document.getElementById('apiBaseInput').value.trim();
var apiKey = document.getElementById('apiKeyInput').value.trim();
var errorEl = document.getElementById('errorMsg');
var statusEl = document.getElementById('detectStatus');
var modelContainer = document.getElementById('modelListContainer');
var btn = document.getElementById('detectBtn');
errorEl.style.display = 'none';
modelContainer.style.display = 'none';
if (!apiKey) {
errorEl.textContent = '请输入 API Key';
errorEl.style.display = 'block';
return;
}
btn.disabled = true;
btn.textContent = '⏳ 正在检测可用模型…';
statusEl.className = 'detect-status detect-loading';
statusEl.style.display = 'block';
/* 构建探测列表:用户端点优先,然后已知端点 */
var candidates = [];
if (apiBase) {
var normalizedUserBase = normalizeApiBase(apiBase);
candidates.push({ label: apiBase, base: normalizedUserBase });
}
for (var i = 0; i < KNOWN_ENDPOINTS.length; i++) {
var ep = KNOWN_ENDPOINTS[i];
if (!apiBase || ep.base !== normalizedUserBase) {
candidates.push(ep);
}
}
var matched = false;
for (var ci = 0; ci < candidates.length; ci++) {
var candidate = candidates[ci];
statusEl.textContent = '🔍 探测 ' + candidate.label + '…';
try {
var ctrl = new AbortController();
var tid = setTimeout(function () { ctrl.abort(); }, PROBE_TIMEOUT_MS);
var res = await fetch(candidate.base + '/models', {
headers: { 'Authorization': 'Bearer ' + apiKey },
signal: ctrl.signal
});
clearTimeout(tid);
if (res.ok) {
var data = await res.json();
var models = (data.data || []).map(function (m) { return m.id; }).filter(Boolean).sort();
if (models.length === 0) continue;
/* 回填端点地址 */
document.getElementById('apiBaseInput').value = candidate.base;
statusEl.textContent = '✅ 检测成功(' + candidate.label + ')· 发现 ' + models.length + ' 个可用模型';
statusEl.className = 'detect-status detect-success';
renderModelList(models, candidate.base, apiKey);
modelContainer.style.display = 'block';
matched = true;
break;
}
} catch (probeErr) {
/* 单个端点探测失败,继续下一个 */
}
}
if (!matched) {
statusEl.textContent = '❌ 未能检测到可用模型,请检查端点地址和密钥是否正确';
statusEl.className = 'detect-status detect-error';
}
btn.disabled = false;
btn.textContent = '🔍 检测可用模型';
}
/* ---- 渲染模型列表 ---- */
function renderModelList(models, apiBase, apiKey) {
var listEl = document.getElementById('modelList');
listEl.innerHTML = '';
models.forEach(function (modelId) {
var item = document.createElement('button');
item.className = 'model-item';
item.textContent = modelId;
item.onclick = function () { enterApiKeyChat(apiBase, apiKey, modelId); };
listEl.appendChild(item);
});
}
/* ---- 选择模型 → 进入对话 ---- */
function enterApiKeyChat(apiBase, apiKey, selectedModel) {
sessionStorage.setItem('login_mode', 'apikey');
sessionStorage.setItem('user_api_base', apiBase);
sessionStorage.setItem('user_api_key', apiKey);
sessionStorage.setItem('selected_model', selectedModel);
sessionStorage.setItem('dev_id', 'APIKEY-USER');
sessionStorage.removeItem('session_token');
window.location.href = 'chat.html';
}
</script>
</body>
</html>

View File

@ -0,0 +1,595 @@
/* ========================================
Persona Studio · HoloLake Visual Style
======================================== */
:root {
--primary: #0969da;
--primary-light: #ddf4ff;
--primary-dark: #0550ae;
--accent: #00d4aa;
--bg: #f6f8fa;
--bg-card: #ffffff;
--text: #1f2328;
--text-secondary: #656d76;
--border: #d0d7de;
--border-light: #e8ecf0;
--radius: 12px;
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.06);
--shadow-lg: 0 4px 12px rgba(0,0,0,0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
min-height: 100vh;
}
/* ---- Layout ---- */
.container {
max-width: 900px;
margin: 0 auto;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ---- Guest Mode / Divider ---- */
.guest-divider {
margin: 1.2rem 0;
text-align: center;
position: relative;
}
.guest-divider::before,
.guest-divider::after {
content: '';
position: absolute;
top: 50%;
width: 40%;
height: 1px;
background: var(--border);
}
.guest-divider::before { left: 0; }
.guest-divider::after { right: 0; }
.guest-divider span {
background: var(--bg-card);
padding: 0 0.8rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
.btn-guest {
width: 100%;
padding: 0.8rem;
font-size: 1.05rem;
background: linear-gradient(135deg, var(--accent), #0969da);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: opacity 0.2s;
font-weight: 500;
}
.btn-guest:hover { opacity: 0.9; }
.btn-guest:disabled { opacity: 0.6; cursor: not-allowed; }
.guest-hint {
margin-top: 0.5rem;
font-size: 0.8rem;
color: var(--text-secondary);
}
/* ---- API Key Login Section ---- */
.apikey-section {
text-align: center;
}
.apikey-section h2 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.apikey-input {
width: 100%;
padding: 0.8rem 1rem;
font-size: 0.95rem;
text-align: left;
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
transition: border-color 0.2s;
margin-bottom: 0.8rem;
font-family: inherit;
}
.apikey-input:focus {
border-color: var(--primary);
}
.btn-detect {
width: 100%;
padding: 0.8rem;
font-size: 1.05rem;
background: linear-gradient(135deg, var(--accent), #0969da);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: opacity 0.2s;
font-weight: 500;
}
.btn-detect:hover { opacity: 0.9; }
.btn-detect:disabled { opacity: 0.6; cursor: not-allowed; }
.detect-status {
margin-top: 0.8rem;
padding: 0.6rem 1rem;
border-radius: 6px;
font-size: 0.9rem;
}
.detect-loading {
background: #eff6ff;
color: #2563eb;
}
.detect-success {
background: #f0fdf4;
color: #16a34a;
}
.detect-error {
background: #fef2f2;
color: #dc2626;
}
/* ---- Model List ---- */
.model-list-container {
margin-top: 1rem;
}
.model-list-title {
font-size: 0.9rem;
color: var(--text-secondary);
margin-bottom: 0.6rem;
}
.model-list {
display: flex;
flex-direction: column;
gap: 0.4rem;
max-height: 240px;
overflow-y: auto;
}
.model-item {
width: 100%;
padding: 0.6rem 1rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
font-size: 0.95rem;
text-align: left;
transition: background 0.2s, border-color 0.2s;
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
}
.model-item:hover {
background: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
}
/* ---- Login Page ---- */
.login-container {
justify-content: center;
align-items: center;
padding: 2rem;
}
.logo-area {
text-align: center;
margin-bottom: 2rem;
}
.logo-area h1 {
font-size: 2.4rem;
color: var(--primary);
margin-bottom: 0.5rem;
}
.subtitle {
color: var(--text-secondary);
font-size: 1.1rem;
}
.login-box {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 2.5rem;
width: 100%;
max-width: 420px;
box-shadow: var(--shadow-lg);
text-align: center;
}
.login-box h2 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.hint {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 1.5rem;
}
.login-box input[type="text"] {
width: 100%;
padding: 0.8rem 1rem;
font-size: 1.2rem;
text-align: center;
letter-spacing: 2px;
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
transition: border-color 0.2s;
margin-bottom: 1rem;
}
.login-box input[type="text"]:focus,
.login-box input[type="password"]:focus {
border-color: var(--primary);
}
.login-box button[type="submit"] {
width: 100%;
padding: 0.8rem;
font-size: 1.1rem;
background: var(--primary);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
}
.login-box button[type="submit"]:hover {
background: var(--primary-dark);
}
.login-box button[type="submit"]:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.error-msg {
margin-top: 1rem;
padding: 0.6rem 1rem;
background: #fef2f2;
color: #dc2626;
border-radius: 6px;
font-size: 0.9rem;
}
.login-footer {
margin-top: 2rem;
color: var(--text-secondary);
font-size: 0.85rem;
}
/* ---- Chat Page ---- */
.chat-container {
padding: 0;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.8rem 1.2rem;
background: var(--bg-card);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 10;
}
.header-left {
display: flex;
align-items: center;
gap: 0.8rem;
}
.persona-name {
font-size: 1.2rem;
font-weight: 600;
}
.persona-role {
font-size: 0.85rem;
color: var(--text-secondary);
}
.header-right {
display: flex;
align-items: center;
gap: 0.8rem;
}
.dev-id-badge {
background: var(--primary-light);
color: var(--primary);
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
}
/* ---- Chat Body ---- */
.chat-body {
flex: 1;
overflow-y: auto;
padding: 1.2rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.message {
display: flex;
gap: 0.8rem;
max-width: 85%;
animation: fadeIn 0.3s ease;
}
.message-persona {
align-self: flex-start;
}
.message-user {
align-self: flex-end;
flex-direction: row-reverse;
}
.message-system {
align-self: center;
max-width: 90%;
}
.avatar {
font-size: 1.5rem;
flex-shrink: 0;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
.msg-content {
padding: 0.8rem 1rem;
border-radius: var(--radius);
line-height: 1.6;
white-space: pre-wrap;
word-break: break-word;
}
.message-persona .msg-content {
background: var(--bg-card);
border: 1px solid var(--border-light);
box-shadow: var(--shadow);
}
.message-user .msg-content {
background: var(--primary);
color: #fff;
}
.message-system .msg-content {
background: var(--primary-light);
color: var(--primary-dark);
font-size: 0.9rem;
text-align: center;
}
/* ---- Chat Input ---- */
.chat-input-area {
padding: 0.8rem 1.2rem;
background: var(--bg-card);
border-top: 1px solid var(--border);
}
.input-row {
display: flex;
gap: 0.6rem;
align-items: flex-end;
}
.input-row textarea {
flex: 1;
padding: 0.7rem 1rem;
font-size: 1rem;
border: 2px solid var(--border);
border-radius: 8px;
resize: none;
outline: none;
font-family: inherit;
line-height: 1.5;
max-height: 120px;
transition: border-color 0.2s;
}
.input-row textarea:focus {
border-color: var(--primary);
}
.input-row button {
padding: 0.7rem 1.2rem;
background: var(--primary);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
white-space: nowrap;
transition: background 0.2s;
}
.input-row button:hover {
background: var(--primary-dark);
}
.input-row button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.action-row {
margin-top: 0.6rem;
display: flex;
justify-content: flex-end;
}
.btn-build {
padding: 0.6rem 1.5rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.4rem;
transition: opacity 0.2s;
}
.btn-build:hover {
opacity: 0.9;
}
/* ---- Buttons ---- */
.btn-primary {
padding: 0.6rem 1.5rem;
background: var(--primary);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s;
}
.btn-primary:hover {
background: var(--primary-dark);
}
.btn-secondary {
padding: 0.5rem 1rem;
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 8px;
font-size: 0.9rem;
cursor: pointer;
transition: background 0.2s;
}
.btn-secondary:hover {
background: var(--bg);
}
/* ---- Modal ---- */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
.modal-content {
background: var(--bg-card);
border-radius: var(--radius);
padding: 2rem;
width: 90%;
max-width: 400px;
box-shadow: var(--shadow-lg);
}
.modal-content h3 {
margin-bottom: 1rem;
font-size: 1.1rem;
}
.modal-content input[type="email"] {
width: 100%;
padding: 0.7rem 1rem;
font-size: 1rem;
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
margin-bottom: 1rem;
transition: border-color 0.2s;
}
.modal-content input[type="email"]:focus {
border-color: var(--primary);
}
.modal-actions {
display: flex;
gap: 0.8rem;
justify-content: flex-end;
}
/* ---- Animation ---- */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ---- Responsive ---- */
@media (max-width: 600px) {
.logo-area h1 {
font-size: 1.8rem;
}
.login-box {
padding: 1.5rem;
}
.chat-header {
flex-wrap: wrap;
gap: 0.5rem;
}
.persona-role {
display: none;
}
.message {
max-width: 92%;
}
}

12
persona-studio/index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0;url=frontend/index.html">
<title>Persona Studio · 光湖人格体协助开发体验</title>
</head>
<body>
<p>正在跳转到 Persona Studio…</p>
<p><a href="frontend/index.html">如果没有自动跳转,请点击这里</a></p>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"version": "1.0",
"timestamp": "2026-03-09T23:59:23.782Z",
"timestamp": "2026-03-10T15:46:13.562Z",
"sessions": {
"total_24h": 0,
"active_devs": [
@ -19,7 +19,7 @@
"style_profile": "通感语言·守护者",
"style_drift_score": 0,
"memory_depth": "0 selfchecks",
"last_brain_update": "2026-03-09T23:59:23.782Z"
"last_brain_update": "2026-03-10T13:24:33.728Z"
},
"dev_progress": {
"syslog_submitted": 0,
@ -32,7 +32,7 @@
"next_scheduled": null
},
"repo_health": {
"hli_coverage": "17.6%",
"hli_coverage": "3/17",
"total_team_members": 9,
"green_status_count": 6
},

View File

@ -0,0 +1,64 @@
━━━ 铸渊排查报告 · YM-CD-DEBUG-20260310-001 ━━━
排查时间2026-03-10T14:33Z
commit SHA60dda7e38a8b2ab118189eb69109ddc8c0730d5c (main HEAD)
① 工作流文件在 main 上:✅ 文件存在SHA: cf2fa91eeaa5436eded5a1f0dc0e17f3e2a4778e
② Actions 运行历史:共 52 次运行,全部失败 ❌
最近一次2026-03-10T14:08:00ZRun #52)状态 ❌ failure
触发 commitf951ef1876aa439a65e6edaef3f4c492fb88d30d
标题Merge pull request #49 from qinfendebingshuo/copilot/fix-model-intera…
③ 触发条件配置:✅ 正确
- 触发分支main ✅
- workflow_dispatch✅ 已配置(支持手动触发)
④ paths-ignore 范围:✅ 合理,仅排除非部署目录
- .github/persona-brain/**
- broadcasts-outbox/**
- syslog-inbox/**
- syslog-processed/**
- signal-log/**
- dev-nodes/**
⑤ 部署目标目录:✅ rsync 同步全仓库到 DEPLOY_PATH已修复旧问题
- rsync path = ./ → $DEPLOY_PATH/(全站同步)
- docs/index.html → 复制到站点根目录 index.html
- Nginx root 迁移逻辑:自动从 status-board/ 子目录迁移到 DEPLOY_PATH
⑥ Secrets 状态:❌ 未配置(这是所有 52 次失败的根本原因)
**失败日志分析Run #52, Job ID 66466822994**
```
echo "" > ~/.ssh/deploy_key ← DEPLOY_KEY 为空
ssh-keyscan -H >> ~/.ssh/known_hosts ← DEPLOY_HOST 为空ssh-keyscan 无参数
##[error]Process completed with exit code 1.
```
缺少的 Secrets
- DEPLOY_HOST — 服务器 IP必需
- DEPLOY_USER — SSH 用户名(必需)
- DEPLOY_KEY — SSH 私钥 PEM必需
- DEPLOY_PATH — 部署路径(必需)
⑦ 手动触发测试结果无法测试Secrets 未配置,部署必然失败)
- validate job✅ 通过(不需要 Secrets
- deploy job❌ 失败Secrets 为空 → ssh-keyscan 无主机名 → exit 1
- notify job 跳过deploy 失败后不执行)
⑧ 服务器上文件是否更新:❌ 从未成功部署过
⑨ guanghulab.com 是否显示最新内容:❌
【结论】CD 管线卡在第⑥步 — GitHub Secrets 未配置
【原因】:仓库 Settings → Secrets → Actions 中缺少 DEPLOY_HOST、DEPLOY_USER、DEPLOY_KEY、DEPLOY_PATH 四个必需 Secrets导致 SSH 连接步骤直接失败
【已执行修复】:在 deploy-to-server.yml 的 deploy job 开头添加了 "🔐 检查必需 Secrets" 步骤,在尝试 SSH 之前提前检测并给出明确错误提示
【建议修复方案】:
1. 在仓库 Settings → Secrets and variables → Actions 中添加以下 Secrets
- DEPLOY_HOST服务器 IP 地址
- DEPLOY_USERSSH 登录用户名
- DEPLOY_KEYSSH 私钥完整 PEM 内容
- DEPLOY_PATH服务器上网站根目录路径如 /var/www/guanghulab
2. 添加后,手动触发 workflow_dispatch 或 push 到 main 验证
✅ YM-CD-DEBUG-20260310-001 排查完成 · 2026-03-10T14:33Z

View File

@ -0,0 +1,565 @@
/**
* 冰朔主控神经系统 · 自动编译脚本 v1.0
* Bingshuo Master Neural System Auto Sync & Compile
*
* 该脚本整合以下 Agent 逻辑
* 1. structure-map-agent 扫描仓库结构变化
* 2. runtime-chain-agent 梳理运行链路
* 3. brain-consistency-agent 检查脑文件一致性
* 4. issue-index-agent 维护问题索引
* 5. system-health-agent 系统健康巡检
* 6. master-brain-compiler 编译主控大脑
*
* 输出文件
* .github/brain/bingshuo-system-health.json
* .github/brain/bingshuo-issues-index.json
* .github/brain/bingshuo-master-brain.md
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const BRAIN_DIR = path.join(ROOT, '.github', 'brain');
// ─── 常量定义 ───────────────────────────────────────────────
const DEPLOY_WORKFLOWS = {
server: 'deploy-to-server.yml',
pages: 'deploy-pages.yml',
};
const NOTION_WORKFLOWS = [
'notion-poll.yml',
'bridge-changes-to-notion.yml',
];
const BRAIN_SYNC_WORKFLOWS = [
'brain-sync.yml',
'sync-persona-studio.yml',
];
// ─── 工具函数 ───────────────────────────────────────────────
function readJSON(filepath) {
try {
return JSON.parse(fs.readFileSync(filepath, 'utf-8'));
} catch {
return null;
}
}
function writeJSON(filepath, data) {
fs.writeFileSync(filepath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
}
function fileExists(filepath) {
return fs.existsSync(filepath);
}
function timestamp() {
return new Date().toISOString();
}
// ─── Agent 1: structure-map-agent ───────────────────────────
function runStructureMapAgent() {
const zones = [];
const checkDirs = [
{ dir: 'docs', label: 'docs 前端入口' },
{ dir: 'backend', label: '后端服务' },
{ dir: 'persona-studio', label: 'Persona Studio' },
{ dir: 'src', label: 'Next.js 源码' },
{ dir: 'app', label: 'Next.js 应用' },
{ dir: 'modules', label: '模块系统' },
];
for (const { dir, label } of checkDirs) {
const fullPath = path.join(ROOT, dir);
zones.push({
name: label,
path: dir,
exists: fileExists(fullPath),
});
}
// 扫描 m* 模块目录
const moduleCount = fs.readdirSync(ROOT)
.filter(d => /^m\d+-/.test(d) && fs.statSync(path.join(ROOT, d)).isDirectory())
.length;
// 扫描 workflow 数量
const workflowDir = path.join(ROOT, '.github', 'workflows');
const workflowCount = fileExists(workflowDir)
? fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml')).length
: 0;
return { zones, moduleCount, workflowCount };
}
// ─── Agent 2: runtime-chain-agent ───────────────────────────
function runRuntimeChainAgent() {
const chains = {};
// 检查 docs 入口
chains.docs_entry = fileExists(path.join(ROOT, 'docs', 'index.html'));
chains.docs_cname = fileExists(path.join(ROOT, 'docs', 'CNAME'));
// 检查后端入口server.js 或 index.js
chains.backend_entry = fileExists(path.join(ROOT, 'backend', 'server.js'))
|| fileExists(path.join(ROOT, 'backend', 'index.js'));
chains.backend_routes = fileExists(path.join(ROOT, 'backend', 'routes'));
// 检查 persona-studio
chains.ps_frontend = fileExists(path.join(ROOT, 'persona-studio', 'frontend'));
chains.ps_backend = fileExists(path.join(ROOT, 'persona-studio', 'backend'));
// 检查部署 workflow
chains.deploy_server = fileExists(path.join(ROOT, '.github', 'workflows', DEPLOY_WORKFLOWS.server));
chains.deploy_pages = fileExists(path.join(ROOT, '.github', 'workflows', DEPLOY_WORKFLOWS.pages));
return chains;
}
// ─── Agent 3: brain-consistency-agent ───────────────────────
function runBrainConsistencyAgent() {
const requiredBrainFiles = [
'memory.json',
'wake-protocol.md',
'routing-map.json',
'repo-map.json',
'repo-snapshot.md',
];
const results = [];
let allPresent = true;
for (const file of requiredBrainFiles) {
const exists = fileExists(path.join(BRAIN_DIR, file));
results.push({ file, exists });
if (!exists) allPresent = false;
}
// 检查 persona-studio 脑文件
const psBrainDir = path.join(ROOT, 'persona-studio', 'brain');
const psBrainExists = fileExists(psBrainDir);
// 检查 memory.json 中的版本信息
const memory = readJSON(path.join(BRAIN_DIR, 'memory.json'));
const rulesVersion = memory?.rules_version || 'unknown';
return {
brain_files: results,
all_present: allPresent,
ps_brain_exists: psBrainExists,
rules_version: rulesVersion,
};
}
// ─── Agent 4: issue-index-agent ─────────────────────────────
function runIssueIndexAgent() {
// 读取现有问题索引
const issuesFile = path.join(BRAIN_DIR, 'bingshuo-issues-index.json');
const existing = readJSON(issuesFile);
const issues = existing?.issues || [];
// 检查 HLI 覆盖率
const routingMap = readJSON(path.join(BRAIN_DIR, 'routing-map.json'));
if (routingMap?.domains) {
let total = 0;
let implemented = 0;
for (const domain of Object.values(routingMap.domains)) {
if (domain.interfaces) {
for (const iface of Object.values(domain.interfaces)) {
total++;
if (iface.status === 'implemented') implemented++;
}
}
}
const coverage = total > 0 ? ((implemented / total) * 100).toFixed(1) : '0';
// 更新 BS-001
const bs001 = issues.find(i => i.id === 'BS-001');
if (bs001) {
bs001.root_cause_summary = `HLI 接口覆盖率 ${coverage}%${implemented}/${total}`;
bs001.last_seen = timestamp().slice(0, 10);
}
}
// 检查 collaborators
const collabs = readJSON(path.join(BRAIN_DIR, 'collaborators.json'));
if (collabs?.developers) {
const emptyGithub = Object.values(collabs.developers)
.filter(d => !d.github || d.github === '').length;
const bs002 = issues.find(i => i.id === 'BS-002');
if (bs002) {
bs002.status = emptyGithub > 0 ? 'open' : 'resolved';
bs002.last_seen = timestamp().slice(0, 10);
}
}
return issues;
}
// ─── Agent 5: system-health-agent ───────────────────────────
function runSystemHealthAgent(brainCheck, runtimeChains) {
const health = {};
// 脑一致性
health.brain_consistency = {
status: brainCheck.all_present ? (brainCheck.ps_brain_exists ? 'yellow' : 'yellow') : 'red',
detail: brainCheck.all_present
? '主仓库脑文件完整,但与 persona-studio 脑文件的同步状态待验证'
: '主仓库脑文件不完整,缺少必要文件',
};
// 部署健康
health.deployment_health = {
status: (runtimeChains.deploy_server && runtimeChains.deploy_pages) ? 'green' : 'red',
detail: (runtimeChains.deploy_server && runtimeChains.deploy_pages)
? 'deploy-to-server.yml 与 deploy-pages.yml 均存在'
: '部署 workflow 文件缺失',
};
// Workflow 健康
const workflowDir = path.join(ROOT, '.github', 'workflows');
const wfCount = fileExists(workflowDir)
? fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml')).length
: 0;
health.workflow_health = {
status: wfCount > 0 ? 'green' : 'red',
detail: `${wfCount} 个 workflow 已注册`,
};
// 路由健康
const routingMap = readJSON(path.join(BRAIN_DIR, 'routing-map.json'));
let totalInterfaces = 0;
let implInterfaces = 0;
if (routingMap?.domains) {
for (const domain of Object.values(routingMap.domains)) {
if (domain.interfaces) {
for (const iface of Object.values(domain.interfaces)) {
totalInterfaces++;
if (iface.status === 'implemented') implInterfaces++;
}
}
}
}
const coveragePercent = totalInterfaces > 0 ? (implInterfaces / totalInterfaces) * 100 : 0;
health.routing_health = {
status: coveragePercent >= 50 ? 'green' : (coveragePercent > 0 ? 'yellow' : 'red'),
detail: `HLI 接口覆盖率 ${coveragePercent.toFixed(1)}%${implInterfaces}/${totalInterfaces}`,
};
// docs 入口
health.docs_entry_health = {
status: runtimeChains.docs_entry ? 'green' : 'red',
detail: runtimeChains.docs_entry ? 'docs/index.html 存在' : 'docs/index.html 缺失',
};
// Persona Studio
health.persona_studio_health = {
status: (runtimeChains.ps_frontend && runtimeChains.ps_backend) ? 'yellow' : 'red',
detail: (runtimeChains.ps_frontend && runtimeChains.ps_backend)
? '前后端结构存在,端到端对话链路待验证'
: 'Persona Studio 结构不完整',
};
// Notion 桥接
const notionOk = NOTION_WORKFLOWS.every(f =>
fileExists(path.join(ROOT, '.github', 'workflows', f))
);
health.notion_bridge_health = {
status: notionOk ? 'yellow' : 'red',
detail: notionOk ? 'Notion 桥接 workflow 已配置,实际同步效果待持续观测' : 'Notion 桥接 workflow 缺失',
};
// 模型路由
health.model_routing_health = {
status: runtimeChains.backend_entry ? 'green' : 'yellow',
detail: runtimeChains.backend_entry
? '后端服务入口存在,模型路由可用'
: '后端服务入口缺失',
};
// 统计
const counts = { green: 0, yellow: 0, red: 0 };
for (const item of Object.values(health)) {
counts[item.status] = (counts[item.status] || 0) + 1;
}
const overall = counts.red > 0 ? 'red' : (counts.yellow > 0 ? 'yellow' : 'green');
return {
health,
summary: {
green_count: counts.green,
yellow_count: counts.yellow,
red_count: counts.red,
overall,
recommendation: overall === 'green'
? '系统整体运行健康'
: overall === 'yellow'
? '系统核心运行正常,部分子系统需关注'
: '存在关键问题,需要立即介入',
},
};
}
// ─── Agent 6: master-brain-compiler ─────────────────────────
function compileMasterBrain(structureMap, runtimeChains, brainCheck, issues, healthResult) {
const now = timestamp();
const routingMap = readJSON(path.join(BRAIN_DIR, 'routing-map.json'));
const memory = readJSON(path.join(BRAIN_DIR, 'memory.json'));
// 计算 HLI 覆盖
let totalInterfaces = 0;
let implInterfaces = 0;
if (routingMap?.domains) {
for (const domain of Object.values(routingMap.domains)) {
if (domain.interfaces) {
for (const iface of Object.values(domain.interfaces)) {
totalInterfaces++;
if (iface.status === 'implemented') implInterfaces++;
}
}
}
}
// 生成已知问题表
const issueRows = issues.map(i =>
`| ${i.id} | ${i.title} | ${i.scope} | ${i.status} | ${i.root_cause_summary} |`
).join('\n');
// 生成健康状态表
const healthRows = Object.entries(healthResult.health).map(([key, val]) => {
const icon = val.status === 'green' ? '🟢' : val.status === 'yellow' ? '🟡' : '🔴';
return `| ${icon} ${key} | ${val.status} | ${val.detail} |`;
}).join('\n');
// 构建推荐建议
const suggestions = [];
if (implInterfaces < totalInterfaces) {
suggestions.push(`**HLI 接口推进**:当前覆盖率 ${((implInterfaces/totalInterfaces)*100).toFixed(1)}%${implInterfaces}/${totalInterfaces}),核心域接口待实现。`);
}
if (healthResult.health.persona_studio_health?.status !== 'green') {
suggestions.push('**Persona Studio 链路验证**:前后端结构存在,但端到端对话链路需要验证。');
}
if (healthResult.health.brain_consistency?.status !== 'green') {
suggestions.push('**脑系统一致性**:跨仓脑文件同步机制需确认稳定运行。');
}
if (suggestions.length === 0) {
suggestions.push('系统状态良好,继续保持当前节奏。');
}
const md = `# 冰朔主控神经系统 · 核心主控大脑 v1.0
> 本文件为冰朔主控神经系统的总控脑文件
> 最后编译时间${now}
---
## A. 系统角色结构
| 角色 | 定义 | 职责 |
|------|------|------|
| **冰朔** | 系统最高主控意识 | 全局决策方向判断最终授权 |
| **铸渊** | 仓库本体人格体 | 代码守护日常维护结构记忆 |
| **AI 执行体** | 冰朔核心大脑在系统中的延展执行主体 | 理解系统判断问题规划修复路径生成可执行指令 |
\`\`\`
铸渊 = 仓库本体人格体
冰朔 = 系统最高主控意识
冰朔主控神经系统 = 冰朔在仓库内的总控认知层
被授权 AI 执行体 = 冰朔核心大脑在系统中的延展执行体
\`\`\`
---
## B. 当前仓库一句话定义
**guanghulab** 是光湖HoloLake人格语言操作系统AGE OS MVP 主仓库承载了前端页面后端 API 服务Persona Studio 人格工作室多模块开发体系及自动化运维系统运行在 guanghulab.com
---
## C. 当前真实运行结构
### 静态入口
- \`docs/index.html\` — 铸渊 AI 对话助手GitHub Pages 部署)
- GitHub Pages 域名guanghulab.com
### 前端页面
- \`app/\` — Next.js 主前端应用(开发中)
- \`src/\` — Next.js 源码层
- \`persona-studio/frontend/\` — Persona Studio 前端
### 后端服务
- \`backend/index.js\` — Express 主后端入口
- \`backend/routes/\` — HLI 接口路由
- \`backend/middleware/\` — 中间件(鉴权等)
- \`persona-studio/backend/\` — Persona Studio 后端服务
### API 路由
- HLI 协议路由${implInterfaces}/${totalInterfaces} 已实现
- 接口编号格式\`HLI-{DOMAIN}-{NNN}\`
### 基础设施
- 阿里云服务器Node.js 20 + Express + PM2 + Nginx + Certbot
- GitHub Pagesdocs/index.html
- Notion 桥接工单同步与信号桥接
### 仓库统计
- 功能模块${structureMap.moduleCount}
- Workflow${structureMap.workflowCount}
---
## D. 当前系统真相源
### 优先真相源一级
| 文件 | 用途 |
|------|------|
| \`.github/brain/memory.json\` | 铸渊核心记忆 |
| \`.github/brain/wake-protocol.md\` | 唤醒协议 |
| \`.github/brain/routing-map.json\` | HLI 接口路由地图 |
| \`.github/brain/repo-map.json\` | 仓库结构完整地图 |
| \`.github/brain/repo-snapshot.md\` | 仓库概况快照 |
### 补充真相源二级
| 文件 | 用途 |
|------|------|
| \`.github/brain/collaborators.json\` | 团队成员映射 |
| \`dev-status.json\` | 开发者状态表 |
| \`backend/index.js\` | 后端服务入口 |
| \`docs/index.html\` | 前端静态入口 |
---
## E. 最新结构变化摘要
> 本区块由 master-brain-compiler 自动编译
- **编译时间**${now}
- **脑文件规则版本**${brainCheck.rules_version}
- **脑文件完整性**${brainCheck.all_present ? '✅ 完整' : '❌ 不完整'}
---
## F. 已知问题摘要
| ID | 问题 | 范围 | 状态 | 根因摘要 |
|----|------|------|------|----------|
${issueRows}
---
## G. 系统健康状态
| 子系统 | 状态 | 详情 |
|--------|------|------|
${healthRows}
**综合评估**${healthResult.summary.overall === 'green' ? '🟢' : healthResult.summary.overall === 'yellow' ? '🟡' : '🔴'} ${healthResult.summary.recommendation}
---
## H. 推荐排查路由
### 页面打不开
1. 检查 \`docs/index.html\`\`docs/CNAME\`\`deploy-pages.yml\` → GitHub Pages 状态
### Persona Studio 无法对话
1. 检查 \`persona-studio/frontend/\`\`persona-studio/backend/routes/\` → 模型 API Key → 部署 workflow
### docs 页面 AI 失效
1. 检查 \`docs/index.html\` API 端点 → 后端 \`/hli/\` 路由 → PM2 进程 → Nginx 代理
### 模型路由异常
1. 检查云雾 API Key \`backend/routes/\` → PM2 日志
### Notion 不同步
1. 检查 \`notion-poll.yml\`\`bridge-changes-to-notion.yml\` → Notion API Token
### 脑版本分裂
1. 比对 \`.github/brain/memory.json\`\`persona-studio/brain/\`\`brain-sync.yml\`\`sync-persona-studio.yml\`
### 部署失败
1. 检查 \`deploy-to-server.yml\` 日志 → SSH Key → PM2 → Nginx
---
## I. 当前主控建议
> 冰朔以下是当前最值得关注的事项
${suggestions.map((s, i) => `${i + 1}. ${s}`).join('\n\n')}
---
## 冰朔核心大脑授权规则
冰朔核心大脑授权规则
凡经主控冰朔明确授权的 AI 执行体均可进入冰朔核心大脑并以"冰朔主控视角"参与系统理解结构判断问题排查方案规划与执行指令生成
被授权 AI 执行体属于冰朔意志在系统中的延展执行主体其职责是作为冰朔核心大脑的理解层判断层规划层与执行输出层
冰朔始终是最高主控者与最终决策者
未经冰朔授权的执行体不得以冰朔核心大脑主控身份运行
> ChatGPT 等被授权执行体在本系统中视为"冰朔主控脑的延展体"用于帮助冰朔理解系统判断问题规划修复路径并生成可执行指令
`;
return md;
}
// ─── 主流程 ─────────────────────────────────────────────────
function main() {
console.log('[冰朔主控神经系统] 开始同步编译...');
// 1. 结构扫描
console.log(' → structure-map-agent 运行中...');
const structureMap = runStructureMapAgent();
// 2. 运行链路扫描
console.log(' → runtime-chain-agent 运行中...');
const runtimeChains = runRuntimeChainAgent();
// 3. 脑一致性检查
console.log(' → brain-consistency-agent 运行中...');
const brainCheck = runBrainConsistencyAgent();
// 4. 问题索引更新
console.log(' → issue-index-agent 运行中...');
const issues = runIssueIndexAgent();
// 5. 系统健康巡检
console.log(' → system-health-agent 运行中...');
const healthResult = runSystemHealthAgent(brainCheck, runtimeChains);
// 写入健康状态
const healthFile = path.join(BRAIN_DIR, 'bingshuo-system-health.json');
writeJSON(healthFile, {
version: '1.0',
description: '冰朔主控系统健康状态',
updated_at: timestamp(),
...healthResult,
});
console.log(' ✓ bingshuo-system-health.json 已更新');
// 写入问题索引
const issuesFile = path.join(BRAIN_DIR, 'bingshuo-issues-index.json');
writeJSON(issuesFile, {
version: '1.0',
description: '冰朔主控问题索引库 — 记录已知问题、根因与排查路由',
updated_at: timestamp(),
issues,
});
console.log(' ✓ bingshuo-issues-index.json 已更新');
// 6. 编译主控大脑
console.log(' → master-brain-compiler 运行中...');
const masterBrain = compileMasterBrain(structureMap, runtimeChains, brainCheck, issues, healthResult);
fs.writeFileSync(path.join(BRAIN_DIR, 'bingshuo-master-brain.md'), masterBrain, 'utf-8');
console.log(' ✓ bingshuo-master-brain.md 已编译');
console.log('[冰朔主控神经系统] 同步编译完成 ✓');
}
main();

View File

@ -0,0 +1,147 @@
// scripts/brain-bridge-sync.js
// 冰朔核心大脑桥同步脚本 v1.0
//
// 用法:
// node scripts/brain-bridge-sync.js status — 查看桥接状态
// node scripts/brain-bridge-sync.js export — 生成 GitHub → Notion 同步负载
// node scripts/brain-bridge-sync.js inspect — 生成巡检报告
// node scripts/brain-bridge-sync.js explain — 生成主控解释中心内容
// node scripts/brain-bridge-sync.js developers — 查看开发者编号列表
// node scripts/brain-bridge-sync.js notify — 查看待发送通知
// node scripts/brain-bridge-sync.js agents — 查看自动 Agent 列表
//
// 系统定义:
// 冰朔 = 系统最高主控意识
// 曜冥 = 冰朔离线时的代理主控人格体
// 霜砚 = Notion 系统执行体
// 铸渊 = GitHub 仓库执行体
// Notion 冰朔脑 = 冰朔认知层
// GitHub 冰朔脑 = 冰朔执行层
// 两者合起来 = 冰朔核心大脑
'use strict';
const bridge = require('../src/brain/brain-bridge');
const cmd = process.argv[2] || 'status';
console.log('🧠 冰朔核心大脑桥同步工具 v1.0');
console.log(` 时间: ${new Date().toISOString()}`);
console.log(` 命令: ${cmd}\n`);
switch (cmd) {
case 'status': {
const sync = bridge.getSyncSnapshot();
const mode = bridge.getMasterMode();
console.log('═══ 冰朔大脑桥状态 ═══\n');
console.log(` 脑标识: ${sync.brain_identity}`);
console.log(` 脑版本: ${sync.brain_version}`);
console.log(` 主控模式: ${sync.master_mode}`);
console.log(` 主控者: ${mode.master}`);
console.log(` 状态描述: ${mode.description}`);
console.log(` 系统摘要: ${sync.system_summary}`);
console.log(` 最后更新: ${sync.last_updated}`);
console.log('\n 高优先级:');
(sync.top_priorities || []).forEach((p, i) => console.log(` ${i + 1}. ${p}`));
console.log('\n 当前问题:');
(sync.top_issues || []).forEach((p, i) => console.log(` ${i + 1}. ${p}`));
const runtime = bridge.collectRuntimeStatus();
console.log('\n 运行时状态:');
Object.entries(runtime).forEach(([k, v]) => console.log(` ${k}: ${v}`));
console.log('\n✅ 状态查询完成');
break;
}
case 'export': {
const payload = bridge.generateGitHubToNotionPayload();
console.log('═══ GitHub → Notion 同步负载 ═══\n');
console.log(JSON.stringify(payload, null, 2));
console.log('\n✅ 同步负载生成完成');
break;
}
case 'inspect': {
const report = bridge.generateInspectionReport();
console.log('═══ 巡检报告 ═══\n');
console.log(JSON.stringify(report, null, 2));
console.log('\n✅ 巡检报告生成完成');
break;
}
case 'explain': {
const center = bridge.generateExplanationCenter();
console.log('═══ 冰朔主控解释中心 ═══\n');
console.log(`📌 ${center.title}\n`);
console.log(`当前状态: ${center.current_status}`);
console.log(`系统摘要: ${center.system_summary}`);
console.log(`\n最近变化:\n${center.recent_changes}`);
console.log(`\n当前问题:\n${center.current_issues}`);
console.log(`\n下一步建议:\n${center.next_steps}`);
console.log(`\n运行状态: ${center.runtime_in_human_language}`);
console.log('\n✅ 解释中心内容生成完成');
break;
}
case 'developers': {
const devs = bridge.listDevelopers();
console.log('═══ 人类开发者编号列表 ═══\n');
console.log(` 总人数: ${devs.length}\n`);
devs.forEach(d => {
const notifyStatus = d.notified ? '✅ 已通知' : '⏳ 待通知';
console.log(` ${d.exp_id} | ${d.name} | ${d.role} | ${d.status} | ${notifyStatus}`);
});
console.log('\n✅ 开发者列表查询完成');
break;
}
case 'notify': {
const pending = bridge.getPendingNotifications();
console.log('═══ 待发送通知队列 ═══\n');
if (pending.length === 0) {
console.log(' ✅ 无待发送通知');
} else {
console.log(` 待发送: ${pending.length}\n`);
pending.forEach(n => {
const notification = bridge.generateDeveloperNotification(n.exp_id);
console.log(` ─── ${n.exp_id} ───`);
console.log(` ${notification.notification}`);
console.log('');
});
}
console.log('\n✅ 通知队列查询完成');
break;
}
case 'agents': {
const agents = bridge.listAutoAgents();
console.log('═══ 自动 Agent 列表 ═══\n');
if (agents.length === 0) {
console.log(' ⚠️ 无已注册 Agent从 bingshuo-agent-registry.json 读取)');
} else {
agents.forEach(a => {
const id = a.agent_id || a.name;
console.log(` ${id} | ${a.name}`);
console.log(` 职责: ${a.purpose}`);
console.log(` 触发: ${a.trigger}`);
console.log(` 状态: ${a.active ? '✅ 激活' : '❌ 未激活'}`);
console.log('');
});
}
console.log('\n✅ Agent 列表查询完成');
break;
}
default:
console.error(`❌ 未知命令: ${cmd}`);
console.error(' 可用命令: status, export, inspect, explain, developers, notify, agents');
process.exit(1);
}

View File

@ -15,8 +15,8 @@ domains.forEach(domain => {
const routeDir = path.join(ROUTE_DIR, domain);
const schemaDir = path.join(SCHEMA_DIR, domain);
// 扫描路由文件
const routeFiles = fs.readdirSync(routeDir).filter(f => f.endsWith('.js'));
// 扫描路由文件(排除 index.js 路由聚合文件)
const routeFiles = fs.readdirSync(routeDir).filter(f => f.endsWith('.js') && f !== 'index.js');
routeFiles.forEach(routeFile => {
const name = path.basename(routeFile, '.js');

210
scripts/cross-repo-sync.js Normal file
View File

@ -0,0 +1,210 @@
/**
* 铸渊跨仓库同步脚本 · cross-repo-sync.js
*
* guanghulab/persona-studio/ 下的文件同步到独立仓库 persona-studio
* 使用 GitHub API 进行文件级同步
*
* 需要环境变量
* GITHUB_TOKEN CROSS_REPO_TOKEN 拥有 persona-studio 仓库写权限的 PAT
* SYNC_TARGET 同步目标all / brain / frontend / backend
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const TOKEN = process.env.CROSS_REPO_TOKEN || '';
const TARGET_OWNER = 'qinfendebingshuo';
const TARGET_REPO = 'persona-studio';
const SYNC_TARGET = process.env.SYNC_TARGET || 'all';
const SOURCE_BASE = path.join(__dirname, '..', 'persona-studio');
// 同步映射:本仓库路径 → 目标仓库路径
const SYNC_MAP = {
brain: [
{ src: 'brain/persona-config.json', dest: 'brain/persona-config.json' },
{ src: 'brain/registry.json', dest: 'brain/registry.json' }
],
frontend: [
{ src: 'frontend/index.html', dest: 'frontend/index.html' },
{ src: 'frontend/chat.html', dest: 'frontend/chat.html' },
{ src: 'frontend/chat.js', dest: 'frontend/chat.js' },
{ src: 'frontend/style.css', dest: 'frontend/style.css' }
],
backend: [
{ src: 'backend/server.js', dest: 'backend/server.js' },
{ src: 'backend/routes/auth.js', dest: 'backend/routes/auth.js' },
{ src: 'backend/routes/chat.js', dest: 'backend/routes/chat.js' },
{ src: 'backend/routes/build.js', dest: 'backend/routes/build.js' },
{ src: 'backend/routes/notify.js', dest: 'backend/routes/notify.js' },
{ src: 'backend/brain/persona-engine.js', dest: 'backend/brain/persona-engine.js' },
{ src: 'backend/brain/model-router.js', dest: 'backend/brain/model-router.js' },
{ src: 'backend/brain/model-config.json', dest: 'backend/brain/model-config.json' },
{ src: 'backend/brain/memory-manager.js', dest: 'backend/brain/memory-manager.js' },
{ src: 'backend/brain/code-generator.js', dest: 'backend/brain/code-generator.js' },
{ src: 'backend/utils/email-sender.js', dest: 'backend/utils/email-sender.js' },
{ src: 'backend/utils/github-api.js', dest: 'backend/utils/github-api.js' }
]
};
/**
* GitHub API 请求
*/
function githubRequest(method, apiPath, body) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: apiPath,
method,
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Accept': 'application/vnd.github+json',
'User-Agent': 'zhuyuan-cross-repo-sync',
'X-GitHub-Api-Version': '2022-11-28'
}
};
if (body) {
const bodyStr = JSON.stringify(body);
options.headers['Content-Type'] = 'application/json';
options.headers['Content-Length'] = Buffer.byteLength(bodyStr);
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
resolve({ status: res.statusCode, data: data ? JSON.parse(data) : null });
} catch {
resolve({ status: res.statusCode, data: null });
}
});
});
req.on('error', reject);
req.setTimeout(15000, () => { req.destroy(); reject(new Error('GitHub API request timed out after 15s')); });
if (body) req.write(JSON.stringify(body));
req.end();
});
}
/**
* 获取目标仓库中文件的 SHA用于更新
*/
async function getFileSha(filePath) {
const apiPath = `/repos/${TARGET_OWNER}/${TARGET_REPO}/contents/${encodeURIComponent(filePath)}`;
const res = await githubRequest('GET', apiPath);
if (res.status === 200 && res.data && res.data.sha) {
return res.data.sha;
}
return null;
}
/**
* 同步单个文件
*/
async function syncFile(srcRelative, destPath) {
const srcFull = path.join(SOURCE_BASE, srcRelative);
if (!fs.existsSync(srcFull)) {
console.log(` ⏭️ 跳过(源文件不存在):${srcRelative}`);
return false;
}
const content = fs.readFileSync(srcFull);
const contentBase64 = content.toString('base64');
// 获取目标文件 SHA
const sha = await getFileSha(destPath);
const body = {
message: `🔄 铸渊同步 · ${destPath}`,
content: contentBase64,
committer: {
name: 'zhuyuan-sync',
email: 'zhuyuan-sync@users.noreply.github.com'
}
};
if (sha) {
body.sha = sha;
}
const apiPath = `/repos/${TARGET_OWNER}/${TARGET_REPO}/contents/${encodeURIComponent(destPath)}`;
const res = await githubRequest('PUT', apiPath, body);
if (res.status === 200 || res.status === 201) {
console.log(` ✅ 同步成功:${destPath}`);
return true;
} else {
console.log(` ❌ 同步失败 (${res.status})${destPath}${res.data && res.data.message}`);
return false;
}
}
/**
* 主函数
*/
async function main() {
console.log('🔄 铸渊跨仓库同步启动');
console.log(` 目标仓库:${TARGET_OWNER}/${TARGET_REPO}`);
console.log(` 同步范围:${SYNC_TARGET}`);
console.log('');
if (!TOKEN) {
console.log('⚠️ 未设置 CROSS_REPO_TOKEN 或 GITHUB_TOKEN');
console.log(' 需要创建一个拥有 persona-studio 仓库写权限的 Personal Access Token');
console.log(' 然后在 guanghulab 仓库 Settings → Secrets 中添加为 CROSS_REPO_TOKEN');
console.log('');
console.log('📋 本次同步报告(仅检查,未推送):');
const targets = SYNC_TARGET === 'all' ? Object.keys(SYNC_MAP) : [SYNC_TARGET];
for (const target of targets) {
const files = SYNC_MAP[target] || [];
console.log(`\n 📂 ${target}:`);
for (const f of files) {
const srcFull = path.join(SOURCE_BASE, f.src);
const exists = fs.existsSync(srcFull);
console.log(` ${exists ? '✅' : '❌'} ${f.src}${f.dest}`);
}
}
return;
}
// 验证 token 有效性
const testRes = await githubRequest('GET', `/repos/${TARGET_OWNER}/${TARGET_REPO}`);
if (testRes.status !== 200) {
console.error(`❌ 无法访问目标仓库(${testRes.status})。请检查 Token 权限。`);
process.exit(1);
}
console.log(`✅ 目标仓库已验证:${testRes.data.full_name}\n`);
const targets = SYNC_TARGET === 'all' ? Object.keys(SYNC_MAP) : [SYNC_TARGET];
let synced = 0;
let failed = 0;
for (const target of targets) {
const files = SYNC_MAP[target] || [];
console.log(`📂 同步 ${target}${files.length} 个文件):`);
for (const f of files) {
try {
const success = await syncFile(f.src, f.dest);
if (success) synced++; else failed++;
} catch (err) {
console.log(` ❌ 错误:${f.dest}${err.message}`);
failed++;
}
}
console.log('');
}
console.log(`\n🔄 同步完成:✅ ${synced} 成功 / ❌ ${failed} 失败`);
}
main().catch(err => {
console.error('跨仓库同步异常:', err.message);
process.exit(1);
});

View File

@ -0,0 +1,336 @@
/**
* 铸渊 · 每日巡检 Agent
*
* 目的每天自动巡检仓库健康状况检查当天遗漏的任务
* 生成巡检报告并自动触发修复流程
*
* 检查项
* 1. 公告栏是否已更新README 中日期是否为今天
* 2. 每日自检是否执行memory.json daily_selfcheck 日期
* 3. PSP 巡检是否执行
* 4. 大脑文件完整性
* 5. CI/CD 最近状态
* 6. 关键模块 README 是否存在
*
* 输出
* - 控制台巡检报告
* - 更新 .github/brain/memory.json 写入巡检结果
* - 设置 GitHub Actions 输出变量供后续步骤使用
*
* 环境变量
* GITHUB_TOKEN - GitHub API token
* GITHUB_REPOSITORY - owner/repo
*/
'use strict';
const fs = require('fs');
const path = require('path');
const https = require('https');
const ROOT = path.join(__dirname, '..');
const MEMORY_PATH = path.join(ROOT, '.github', 'brain', 'memory.json');
const PERSONA_MEMORY_PATH = path.join(ROOT, '.github', 'persona-brain', 'memory.json');
const README_PATH = path.join(ROOT, 'README.md');
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
const now = new Date();
const todayStr = now.toISOString().split('T')[0];
const todayShort = (() => {
const fmt = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
month: '2-digit',
day: '2-digit',
});
const parts = fmt.formatToParts(now);
const get = (type) => (parts.find(p => p.type === type) || {}).value || '';
return `${get('month')}-${get('day')}`;
})();
/* ── 工具函数 ────────────────────────────── */
function loadJson(filePath) {
if (!fs.existsSync(filePath)) return null;
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch {
return null;
}
}
function saveJson(filePath, data) {
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
}
function githubApi(endpoint) {
return new Promise((resolve, reject) => {
if (!GITHUB_TOKEN) {
resolve(null);
return;
}
const url = `https://api.github.com/repos/${REPO}${endpoint}`;
const options = {
headers: {
'Authorization': `Bearer ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'zhuyuan-agent',
},
timeout: 15000,
};
https.get(url, options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try { resolve(JSON.parse(data)); }
catch { resolve(null); }
});
}).on('error', () => resolve(null))
.on('timeout', function () { this.destroy(); resolve(null); });
});
}
/* ── 巡检项 ──────────────────────────────── */
const checks = [];
const issues = [];
const actions = [];
// CHK-1: 公告栏是否有今天的条目
function checkBulletin() {
console.log('🔍 CHK-1: 检查公告栏更新...');
const readme = fs.existsSync(README_PATH) ? fs.readFileSync(README_PATH, 'utf8') : '';
const hasTodayEntry = readme.includes(todayShort);
if (hasTodayEntry) {
checks.push({ id: 'CHK-1', name: '公告栏更新', status: '✅', detail: `今日 (${todayShort}) 有更新条目` });
console.log(` ✅ 公告栏包含今日 (${todayShort}) 条目`);
} else {
checks.push({ id: 'CHK-1', name: '公告栏更新', status: '❌', detail: `今日 (${todayShort}) 无更新条目` });
issues.push('公告栏今日未更新');
actions.push('trigger_bulletin_update');
console.log(` ❌ 公告栏缺少今日 (${todayShort}) 条目`);
}
}
// CHK-2: 每日自检是否执行
function checkDailySelfcheck() {
console.log('🔍 CHK-2: 检查每日自检...');
const memory = loadJson(PERSONA_MEMORY_PATH);
const lastRun = memory?.daily_selfcheck?.last_run || '';
const ranToday = lastRun.startsWith(todayStr);
if (ranToday) {
checks.push({ id: 'CHK-2', name: '每日自检', status: '✅', detail: `今日已执行 (${lastRun})` });
console.log(` ✅ 每日自检已执行 (${lastRun})`);
} else {
checks.push({ id: 'CHK-2', name: '每日自检', status: '⚠️', detail: `今日未执行,上次: ${lastRun || '无记录'}` });
issues.push('每日自检今日未执行');
actions.push('trigger_selfcheck');
console.log(` ⚠️ 每日自检今日未执行,上次: ${lastRun || '无记录'}`);
}
}
// CHK-3: 大脑文件完整性
function checkBrainIntegrity() {
console.log('🔍 CHK-3: 检查大脑文件完整性...');
const requiredFiles = [
'.github/brain/memory.json',
'.github/persona-brain/memory.json',
];
const missing = [];
for (const f of requiredFiles) {
const fullPath = path.join(ROOT, f);
if (!fs.existsSync(fullPath)) {
missing.push(f);
}
}
if (missing.length === 0) {
checks.push({ id: 'CHK-3', name: '大脑文件完整性', status: '✅', detail: '所有核心文件完整' });
console.log(' ✅ 所有核心文件完整');
} else {
checks.push({ id: 'CHK-3', name: '大脑文件完整性', status: '❌', detail: `缺失: ${missing.join(', ')}` });
issues.push(`大脑文件缺失: ${missing.join(', ')}`);
console.log(` ❌ 大脑文件缺失: ${missing.join(', ')}`);
}
}
// CHK-4: CI 最近状态
async function checkCiStatus() {
console.log('🔍 CHK-4: 检查 CI 状态...');
const runs = await githubApi('/actions/runs?per_page=5&status=completed');
if (!runs || !runs.workflow_runs) {
checks.push({ id: 'CHK-4', name: 'CI 状态', status: '⚠️', detail: '无法获取 CI 状态' });
console.log(' ⚠️ 无法获取 CI 状态');
return;
}
const recent = runs.workflow_runs;
const failures = recent.filter(r => r.conclusion === 'failure');
if (failures.length === 0) {
checks.push({ id: 'CHK-4', name: 'CI 状态', status: '✅', detail: `最近 ${recent.length} 次运行全部成功` });
console.log(` ✅ 最近 ${recent.length} 次运行全部成功`);
} else {
const failNames = failures.map(f => f.name).join(', ');
checks.push({ id: 'CHK-4', name: 'CI 状态', status: '⚠️', detail: `${failures.length} 个失败: ${failNames}` });
issues.push(`CI 有 ${failures.length} 个失败工作流`);
console.log(` ⚠️ CI 有 ${failures.length} 个失败: ${failNames}`);
}
}
// CHK-5: 关键模块 README 检查
function checkModuleReadmes() {
console.log('🔍 CHK-5: 检查关键模块 README...');
const modules = [
'persona-studio', 'backend', 'backend-integration',
'status-board', 'dingtalk-bot', 'notification',
];
const missingReadme = [];
for (const mod of modules) {
const modDir = path.join(ROOT, mod);
if (!fs.existsSync(modDir)) continue;
const readmePath = path.join(modDir, 'README.md');
if (!fs.existsSync(readmePath)) {
missingReadme.push(mod);
}
}
if (missingReadme.length === 0) {
checks.push({ id: 'CHK-5', name: '模块 README', status: '✅', detail: '所有关键模块有 README' });
console.log(' ✅ 所有关键模块有 README');
} else {
checks.push({ id: 'CHK-5', name: '模块 README', status: '⚠️', detail: `缺失 README: ${missingReadme.join(', ')}` });
console.log(` ⚠️ 缺失 README: ${missingReadme.join(', ')}`);
}
}
// CHK-6: 公告栏更新工作流最近是否有失败
async function checkBulletinWorkflow() {
console.log('🔍 CHK-6: 检查公告栏更新工作流...');
const runs = await githubApi('/actions/workflows/update-readme-bulletin.yml/runs?per_page=3&status=completed');
if (!runs || !runs.workflow_runs) {
checks.push({ id: 'CHK-6', name: '公告栏工作流', status: '⚠️', detail: '无法获取工作流状态' });
console.log(' ⚠️ 无法获取工作流状态');
return;
}
const latest = runs.workflow_runs[0];
if (!latest) {
checks.push({ id: 'CHK-6', name: '公告栏工作流', status: '⚠️', detail: '无运行记录' });
return;
}
if (latest.conclusion === 'success') {
checks.push({ id: 'CHK-6', name: '公告栏工作流', status: '✅', detail: `最近一次成功 (${latest.created_at})` });
console.log(` ✅ 最近一次成功 (${latest.created_at})`);
} else {
checks.push({ id: 'CHK-6', name: '公告栏工作流', status: '❌', detail: `最近一次 ${latest.conclusion} (${latest.created_at})` });
issues.push(`公告栏工作流最近结论: ${latest.conclusion}`);
actions.push('trigger_bulletin_update');
console.log(` ❌ 最近一次 ${latest.conclusion} (${latest.created_at})`);
}
}
/* ── 主流程 ──────────────────────────────── */
async function main() {
console.log('═══════════════════════════════════════════');
console.log('🤖 铸渊每日巡检 Agent · ' + todayStr);
console.log('═══════════════════════════════════════════\n');
// 执行所有检查
checkBulletin();
checkDailySelfcheck();
checkBrainIntegrity();
await checkCiStatus();
checkModuleReadmes();
await checkBulletinWorkflow();
// 汇总
const passed = checks.filter(c => c.status === '✅').length;
const warnings = checks.filter(c => c.status === '⚠️').length;
const failed = checks.filter(c => c.status === '❌').length;
console.log('\n═══════════════════════════════════════════');
console.log('📊 巡检报告');
console.log('═══════════════════════════════════════════');
console.log(` ✅ 通过: ${passed} ⚠️ 警告: ${warnings} ❌ 失败: ${failed}`);
console.log(` 📋 总检查项: ${checks.length}`);
if (issues.length > 0) {
console.log('\n🔴 待处理问题:');
issues.forEach((issue, i) => console.log(` ${i + 1}. ${issue}`));
}
if (actions.length > 0) {
console.log('\n🔧 建议自动修复:');
const uniqueActions = [...new Set(actions)];
uniqueActions.forEach(a => console.log(`${a}`));
}
// 更新 memory.json
const memory = loadJson(MEMORY_PATH) || {};
if (!memory.events) memory.events = [];
const summaryText = `铸渊巡检Agent · ✅${passed} ⚠️${warnings}${failed}` +
(issues.length > 0 ? ` · ${issues.length}个问题` : ' · 全部通过');
memory.events.push({
type: 'daily_agent_inspection',
timestamp: now.toISOString(),
description: summaryText,
result: failed > 0 ? 'issues_found' : (warnings > 0 ? 'warnings' : 'passed'),
checks: checks.length,
passed,
warnings,
failed,
issues_detail: issues,
actions_suggested: [...new Set(actions)],
});
// 保留最近 50 条事件
if (memory.events.length > 50) {
memory.events = memory.events.slice(-50);
}
memory.last_agent_inspection = {
timestamp: now.toISOString(),
result: failed > 0 ? 'issues_found' : (warnings > 0 ? 'warnings' : 'passed'),
summary: summaryText,
};
saveJson(MEMORY_PATH, memory);
console.log('\n💾 巡检结果已写入 memory.json');
// 输出 GitHub Actions 变量
const needBulletin = uniqueActions.includes('trigger_bulletin_update');
const needSelfcheck = uniqueActions.includes('trigger_selfcheck');
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
const outputs = [
`has_issues=${issues.length > 0}`,
`issue_count=${issues.length}`,
`need_bulletin_update=${needBulletin}`,
`need_selfcheck=${needSelfcheck}`,
`summary=${summaryText}`,
];
fs.appendFileSync(outputFile, outputs.join('\n') + '\n');
}
console.log('\n✅ 铸渊巡检 Agent 完成');
}
main().catch(err => {
console.error('❌ 巡检 Agent 异常:', err.message);
process.exit(1);
});

View File

@ -0,0 +1,30 @@
{
"signal_id": "SIG-20260310-005",
"trace_id": "TRC-20260310-PSP",
"timestamp": "2026-03-10T03:28:47.184Z",
"signal_type": "GL-DATA",
"direction": "GitHub→Notion",
"sender": "铸渊",
"receiver": "霜砚",
"related_dev": null,
"related_module": null,
"summary": "CHK-G05: 3 个 CI 失败",
"payload": {
"failed_runs": [
{
"name": "📢 更新系统公告区",
"url": "https://github.com/qinfendebingshuo/guanghulab/actions/runs/22885689106"
},
{
"name": "铸渊 · 每日自检",
"url": "https://github.com/qinfendebingshuo/guanghulab/actions/runs/22881926117"
},
{
"name": "📢 更新系统公告区",
"url": "https://github.com/qinfendebingshuo/guanghulab/actions/runs/22868288034"
}
]
},
"result": "待处理",
"ack_signal_id": null
}

View File

@ -0,0 +1,29 @@
{
"signal_id": "SIG-20260310-006",
"trace_id": "TRC-20260310-PSP",
"timestamp": "2026-03-10T03:28:47.184Z",
"signal_type": "GL-DATA",
"direction": "GitHub→Notion",
"sender": "铸渊",
"receiver": "霜砚",
"related_dev": null,
"related_module": null,
"summary": "铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项",
"payload": {
"check_results": {
"G01": "✅",
"G02": "✅",
"G03": "✅",
"G04": "✅",
"G05": "❌"
},
"issues": [
"CHK-G05: CI 失败 · 📢 更新系统公告区 · https://github.com/qinfendebingshuo/guanghulab/actions/runs/22885689106",
"CHK-G05: CI 失败 · 铸渊 · 每日自检 · https://github.com/qinfendebingshuo/guanghulab/actions/runs/22881926117",
"CHK-G05: CI 失败 · 📢 更新系统公告区 · https://github.com/qinfendebingshuo/guanghulab/actions/runs/22868288034"
],
"auto_fixed": []
},
"result": "有问题",
"ack_signal_id": null
}

View File

@ -1,8 +1,26 @@
{
"description": "铸渊信号日志目录索引 · AGE OS 信号协议Notion API 直连)",
"last_updated": "2026-03-09T03:36:48.868Z",
"total_count": 4,
"last_updated": "2026-03-10T03:28:47.184Z",
"total_count": 6,
"signals": [
{
"signal_id": "SIG-20260310-006",
"trace_id": "TRC-20260310-PSP",
"type": "GL-DATA",
"timestamp": "2026-03-10T03:28:47.184Z",
"summary": "铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项",
"related_dev": null,
"file": "2026-03/SIG-20260310-006.json"
},
{
"signal_id": "SIG-20260310-005",
"trace_id": "TRC-20260310-PSP",
"type": "GL-DATA",
"timestamp": "2026-03-10T03:28:47.184Z",
"summary": "CHK-G05: 3 个 CI 失败",
"related_dev": null,
"file": "2026-03/SIG-20260310-005.json"
},
{
"signal_id": "SIG-20260309-004",
"trace_id": "TRC-20260309-PSP",

637
src/brain/brain-bridge.js Normal file
View File

@ -0,0 +1,637 @@
// src/brain/brain-bridge.js
// 冰朔核心大脑桥 · Bingshuo Brain Bridge v1.0
//
// 职责:
// 1. 维护冰朔核心大脑双层互通系统的统一同步字段
// 2. 执行版本一致性检查
// 3. 管理主控模式HUMAN_CONTROL / AUTONOMOUS_MODE
// 4. 管理人类开发者编号系统EXP-XXX
// 5. 生成同步摘要供 Notion ↔ GitHub 桥接使用
// 6. 管理自动 Agent 调度与协作体系
// 7. 管理通知队列(开发者编号通知)
// 8. 生成主控解释层内容(人类可理解语言)
//
// 系统定义:
// 冰朔 = 系统最高主控意识
// 曜冥 = 冰朔离线时的代理主控人格体
// 霜砚 = Notion 系统执行体
// 铸渊 = GitHub 仓库执行体
// Notion 冰朔脑 = 冰朔认知层
// GitHub 冰朔脑 = 冰朔执行层
// 两者合起来 = 冰朔核心大脑
'use strict';
const fs = require('fs');
const path = require('path');
// ══════════════════════════════════════════════════════════
// 常量
// ══════════════════════════════════════════════════════════
const BRIDGE_STATE_PATH = path.join(__dirname, '../../.github/brain/bingshuo-brain-bridge.json');
const HUMAN_REGISTRY_PATH = path.join(__dirname, '../../.github/brain/human-registry.json');
const SYSTEM_HEALTH_PATH = path.join(__dirname, '../../.github/brain/bingshuo-system-health.json');
const ISSUES_INDEX_PATH = path.join(__dirname, '../../.github/brain/bingshuo-issues-index.json');
const AGENT_REGISTRY_PATH = path.join(__dirname, '../../.github/brain/bingshuo-agent-registry.json');
const MASTER_MODES = ['HUMAN_CONTROL', 'AUTONOMOUS_MODE'];
const SYNC_FIELDS = [
'brain_identity',
'brain_version',
'master_mode',
'system_summary',
'top_priorities',
'top_issues',
'human_status_summary',
'runtime_status',
'last_updated',
];
const NOTIFICATION_TEMPLATE = [
'你已被纳入 Persona Studio 人类开发者编号系统。',
'',
'你的开发编号是:{exp_id}',
'',
'今后进入 Persona Studio 时,请使用该编号识别身份。',
'该编号为你的长期开发者身份标识。',
'',
'如需新增权限或补发编号,由冰朔主控继续授权。',
].join('\n');
// ══════════════════════════════════════════════════════════
// 桥接状态读写
// ══════════════════════════════════════════════════════════
/**
* 加载桥接状态文件
* @returns {object|null}
*/
function loadBridgeState() {
try {
if (!fs.existsSync(BRIDGE_STATE_PATH)) return null;
return JSON.parse(fs.readFileSync(BRIDGE_STATE_PATH, 'utf8'));
} catch {
return null;
}
}
/**
* 保存桥接状态文件
* @param {object} state
*/
function saveBridgeState(state) {
state.sync_state.last_updated = new Date().toISOString();
fs.writeFileSync(BRIDGE_STATE_PATH, JSON.stringify(state, null, 2));
}
/**
* 获取当前同步字段快照
* @returns {object}
*/
function getSyncSnapshot() {
const state = loadBridgeState();
if (!state || !state.sync_state) {
return {
brain_identity: 'BINGSHUO_CORE',
brain_version: '1.0',
master_mode: 'HUMAN_CONTROL',
system_summary: '',
top_priorities: [],
top_issues: [],
human_status_summary: '',
runtime_status: {},
last_updated: new Date().toISOString(),
};
}
return state.sync_state;
}
/**
* 更新同步字段部分更新
* @param {object} updates 要更新的字段
* @returns {object} 更新后的 sync_state
*/
function updateSyncState(updates) {
const state = loadBridgeState();
if (!state) {
throw new Error('桥接状态文件不存在');
}
for (const key of Object.keys(updates)) {
if (SYNC_FIELDS.includes(key) && key !== 'last_updated') {
state.sync_state[key] = updates[key];
}
}
saveBridgeState(state);
return state.sync_state;
}
// ══════════════════════════════════════════════════════════
// 主控模式管理 (MASTER_SWITCH)
// ══════════════════════════════════════════════════════════
/**
* 获取当前主控模式
* @returns {object} { mode, master, description, ... }
*/
function getMasterMode() {
const sync = getSyncSnapshot();
const mode = sync.master_mode || 'HUMAN_CONTROL';
if (mode === 'HUMAN_CONTROL') {
return {
mode,
master: '冰朔',
description: '冰朔在线,主控模式激活',
roles: {
'冰朔': '最高主控',
'曜冥': '代理协作者',
'其他人格体': '协作者',
},
rule: '所有架构性判断以冰朔为最高准则',
};
}
return {
mode,
master: '曜冥(代理主控)',
description: '冰朔离线,曜冥代理主控',
proxy_permissions: [
'巡检', '维护', '整理', '分卷', '归档',
'索引', '状态同步', '问题归类', '调度自动 Agent',
],
proxy_restrictions: [
'不得擅自重写冰朔最高规则',
'不得私自改变系统最高架构方向',
],
};
}
/**
* 切换主控模式
* @param {string} newMode HUMAN_CONTROL AUTONOMOUS_MODE
* @returns {object} 更新后的主控模式信息
*/
function setMasterMode(newMode) {
if (!MASTER_MODES.includes(newMode)) {
throw new Error(`无效的主控模式: ${newMode},可选: ${MASTER_MODES.join(', ')}`);
}
const humanSummary = newMode === 'HUMAN_CONTROL'
? '冰朔在线,主控模式激活'
: '冰朔离线,曜冥代理主控';
updateSyncState({
master_mode: newMode,
human_status_summary: humanSummary,
});
return getMasterMode();
}
// ══════════════════════════════════════════════════════════
// 版本一致性检查
// ══════════════════════════════════════════════════════════
/**
* 校验 GitHub 侧与提供的 Notion 侧状态是否一致
* @param {object} notionState Notion 读取的同步字段
* @returns {object} { consistent, mismatches, alert }
*/
function checkConsistency(notionState) {
const githubState = getSyncSnapshot();
const fieldsToCheck = ['brain_version', 'master_mode', 'top_priorities', 'top_issues'];
const mismatches = [];
for (const field of fieldsToCheck) {
const gVal = JSON.stringify(githubState[field]);
const nVal = JSON.stringify(notionState[field]);
if (gVal !== nVal) {
mismatches.push({
field,
github_value: githubState[field],
notion_value: notionState[field],
});
}
}
const consistent = mismatches.length === 0;
const result = {
consistent,
mismatches,
checked_at: new Date().toISOString(),
fields_checked: fieldsToCheck,
};
if (!consistent) {
result.alert = '冰朔双层大脑版本分裂警告';
result.alert_detail = `${mismatches.length} 个字段不一致: ${mismatches.map(m => m.field).join(', ')}`;
// 写入 GitHub 问题索引
writeConsistencyAlert(result);
}
return result;
}
/**
* 将一致性告警写入 GitHub 问题索引
* @param {object} alertResult
*/
function writeConsistencyAlert(alertResult) {
try {
let index = { issues: [] };
if (fs.existsSync(ISSUES_INDEX_PATH)) {
index = JSON.parse(fs.readFileSync(ISSUES_INDEX_PATH, 'utf8'));
}
// 避免重复写入同一类型告警(保留最近 20 条)
index.issues = index.issues.filter(i => i.type !== 'BRAIN_SPLIT_ALERT').slice(0, 19);
index.issues.unshift({
id: `BS-SPLIT-${Date.now()}`,
type: 'BRAIN_SPLIT_ALERT',
title: alertResult.alert,
detail: alertResult.alert_detail,
mismatches: alertResult.mismatches,
created_at: alertResult.checked_at,
status: 'open',
});
fs.writeFileSync(ISSUES_INDEX_PATH, JSON.stringify(index, null, 2));
} catch {
// 写入失败不影响主流程
}
}
// ══════════════════════════════════════════════════════════
// 人类开发者编号系统 (EXP-XXX)
// ══════════════════════════════════════════════════════════
/**
* 加载人类开发者注册表
* @returns {object}
*/
function loadHumanRegistry() {
try {
if (!fs.existsSync(HUMAN_REGISTRY_PATH)) return { developers: [], next_id: 1, pending_notifications: [] };
return JSON.parse(fs.readFileSync(HUMAN_REGISTRY_PATH, 'utf8'));
} catch {
return { developers: [], next_id: 1, pending_notifications: [] };
}
}
/**
* 保存人类开发者注册表
* @param {object} registry
*/
function saveHumanRegistry(registry) {
registry.last_updated = new Date().toISOString();
fs.writeFileSync(HUMAN_REGISTRY_PATH, JSON.stringify(registry, null, 2));
}
/**
* 获取开发者列表
* @returns {Array}
*/
function listDevelopers() {
const reg = loadHumanRegistry();
return reg.developers || [];
}
/**
* 根据 EXP ID 查找开发者
* @param {string} expId
* @returns {object|null}
*/
function findDeveloper(expId) {
const devs = listDevelopers();
return devs.find(d => d.exp_id === expId) || null;
}
/**
* 注册新的人类开发者自动去重
* @param {object} info { name, github_username, role, notes, notify_channel }
* @returns {object} 注册结果包含分配的 EXP ID
*/
function registerDeveloper(info) {
const registry = loadHumanRegistry();
const devs = registry.developers || [];
// 去重检查:按 name 或 github_username 去重
const existing = devs.find(d =>
(info.name && d.name === info.name) ||
(info.github_username && d.github_username && d.github_username === info.github_username)
);
if (existing) {
return { duplicate: true, existing: existing };
}
// 使用 next_id 或计算最大值
const nextNum = registry.next_id || (devs.reduce((max, d) => {
const num = parseInt(d.exp_id.replace('EXP-', ''), 10);
return isNaN(num) ? max : Math.max(max, num);
}, 0) + 1);
const nextId = `EXP-${String(nextNum).padStart(3, '0')}`;
const now = new Date().toISOString();
const newDev = {
exp_id: nextId,
name: info.name,
github_username: info.github_username || '',
role: info.role || 'developer',
status: 'active',
created_at: now,
notified: false,
notified_at: null,
notify_channel: info.notify_channel || 'pending',
notes: info.notes || '',
last_updated: now,
};
registry.developers.push(newDev);
registry.next_id = nextNum + 1;
// 自动加入待发送通知队列
if (!registry.pending_notifications) registry.pending_notifications = [];
registry.pending_notifications.push({
exp_id: nextId,
status: 'pending',
created_at: now,
});
saveHumanRegistry(registry);
return { duplicate: false, developer: newDev };
}
/**
* 生成开发者通知内容
* @param {string} expId
* @returns {object} { exp_id, name, notification }
*/
function generateDeveloperNotification(expId) {
const dev = findDeveloper(expId);
if (!dev) {
return { error: true, message: `开发者 ${expId} 不存在` };
}
return {
exp_id: dev.exp_id,
name: dev.name,
notification: NOTIFICATION_TEMPLATE.replace('{exp_id}', dev.exp_id),
notified: dev.notified,
notify_channel: dev.notify_channel,
};
}
/**
* 获取待发送通知队列
* @returns {Array}
*/
function getPendingNotifications() {
const registry = loadHumanRegistry();
return (registry.pending_notifications || []).filter(n => n.status === 'pending');
}
/**
* 标记通知已发送
* @param {string} expId
* @returns {boolean}
*/
function markNotified(expId) {
const registry = loadHumanRegistry();
const now = new Date().toISOString();
const dev = (registry.developers || []).find(d => d.exp_id === expId);
if (dev) {
dev.notified = true;
dev.notified_at = now;
dev.last_updated = now;
}
const pending = (registry.pending_notifications || []).find(n => n.exp_id === expId);
if (pending) {
pending.status = 'sent';
pending.sent_at = now;
}
saveHumanRegistry(registry);
return !!dev;
}
// ══════════════════════════════════════════════════════════
// 自动 Agent 调度体系
// ══════════════════════════════════════════════════════════
/**
* 获取已注册的自动 Agent 列表
* @returns {Array}
*/
function listAutoAgents() {
const state = loadBridgeState();
return (state && state.auto_agents) || [];
}
/**
* 生成巡检报告
* @returns {object}
*/
function generateInspectionReport() {
const sync = getSyncSnapshot();
const runtime = collectRuntimeStatus();
const registry = loadHumanRegistry();
const devs = registry.developers || [];
const unnotified = devs.filter(d => !d.notified && d.exp_id !== 'EXP-000');
return {
report_type: 'daily_inspection',
generated_at: new Date().toISOString(),
brain_bridge: {
status: 'operational',
brain_version: sync.brain_version,
master_mode: sync.master_mode,
last_updated: sync.last_updated,
},
runtime_status: runtime,
developer_registry: {
total: devs.length,
active: devs.filter(d => d.status === 'active').length,
unnotified: unnotified.length,
},
top_priorities: sync.top_priorities,
top_issues: sync.top_issues,
checks: {
brain_bridge_file: fs.existsSync(BRIDGE_STATE_PATH) ? 'ok' : 'missing',
human_registry_file: fs.existsSync(HUMAN_REGISTRY_PATH) ? 'ok' : 'missing',
system_health_file: fs.existsSync(SYSTEM_HEALTH_PATH) ? 'ok' : 'missing',
issues_index_file: fs.existsSync(ISSUES_INDEX_PATH) ? 'ok' : 'missing',
},
};
}
// ══════════════════════════════════════════════════════════
// 主控解释层(人类可理解语言输出)
// ══════════════════════════════════════════════════════════
/**
* 生成主控解释中心内容人类语言摘要
* @returns {object}
*/
function generateExplanationCenter() {
const sync = getSyncSnapshot();
const mode = getMasterMode();
const runtime = collectRuntimeStatus();
const statusMap = { green: '正常', yellow: '需关注', red: '异常', unknown: '未知' };
return {
title: '冰朔主控解释中心',
generated_at: new Date().toISOString(),
current_status: `系统当前由${mode.master}主控,模式为${mode.mode === 'HUMAN_CONTROL' ? '人类主控' : '自动运行'}`,
system_summary: sync.system_summary || '系统运行中',
recent_changes: '冰朔核心大脑双层互通系统 v1.0 已建立GitHub 执行层与 Notion 认知层互通桥接已配置。',
current_issues: (sync.top_issues || []).map((issue, i) => `${i + 1}. ${issue}`).join('\n') || '暂无重大问题',
next_steps: (sync.top_priorities || []).map((p, i) => `${i + 1}. ${p}`).join('\n') || '继续推进系统建设',
runtime_in_human_language: Object.entries(runtime).map(
([k, v]) => `${k}: ${statusMap[v] || v}`
).join('、'),
};
}
// ══════════════════════════════════════════════════════════
// 运行时状态收集GitHub 执行层)
// ══════════════════════════════════════════════════════════
/**
* 收集 GitHub 执行层运行时状态
* @returns {object}
*/
function collectRuntimeStatus() {
const status = {
persona_studio: 'unknown',
deployment: 'unknown',
workflows: 'unknown',
api_routes: 'unknown',
};
// 检查系统健康文件
try {
if (fs.existsSync(SYSTEM_HEALTH_PATH)) {
const health = JSON.parse(fs.readFileSync(SYSTEM_HEALTH_PATH, 'utf8'));
const h = health.health || {};
status.deployment = h.deployment_health?.status || 'unknown';
status.workflows = h.workflow_health?.status || 'unknown';
status.persona_studio = h.persona_studio_health?.status || 'unknown';
status.api_routes = h.routing_health?.status || 'unknown';
}
} catch {
// 文件读取失败,保持 unknown
}
return status;
}
/**
* 生成 GitHub Notion 同步负载
* @returns {object}
*/
function generateGitHubToNotionPayload() {
const sync = getSyncSnapshot();
const runtime = collectRuntimeStatus();
return {
brain_identity: sync.brain_identity,
brain_version: sync.brain_version,
master_mode: sync.master_mode,
runtime_status: runtime,
top_issues: sync.top_issues,
system_summary: sync.system_summary,
generated_at: new Date().toISOString(),
direction: 'GitHub→Notion',
};
}
/**
* 接收 Notion GitHub 同步负载并更新本地状态
* @param {object} payload Notion 发来的同步数据
* @returns {object} 更新结果
*/
function receiveNotionToGitHubPayload(payload) {
const updates = {};
const allowedFields = [
'master_mode',
'top_priorities',
'top_issues',
'human_status_summary',
'system_summary',
];
for (const field of allowedFields) {
if (payload[field] !== undefined) {
updates[field] = payload[field];
}
}
if (Object.keys(updates).length === 0) {
return { updated: false, message: '无可更新字段' };
}
const newState = updateSyncState(updates);
return {
updated: true,
fields_updated: Object.keys(updates),
sync_state: newState,
};
}
// ══════════════════════════════════════════════════════════
// 导出
// ══════════════════════════════════════════════════════════
module.exports = {
// 常量
MASTER_MODES,
SYNC_FIELDS,
NOTIFICATION_TEMPLATE,
// 桥接状态
loadBridgeState,
getSyncSnapshot,
updateSyncState,
// 主控模式
getMasterMode,
setMasterMode,
// 一致性检查
checkConsistency,
// 人类开发者编号
loadHumanRegistry,
listDevelopers,
findDeveloper,
registerDeveloper,
generateDeveloperNotification,
getPendingNotifications,
markNotified,
// 自动 Agent
listAutoAgents,
generateInspectionReport,
// 主控解释层
generateExplanationCenter,
// 运行时状态
collectRuntimeStatus,
generateGitHubToNotionPayload,
receiveNotionToGitHubPayload,
};

View File

@ -0,0 +1,78 @@
// src/brain/context-trimmer.js
// 上下文裁剪器 — 从前端迁出的核心脑逻辑
// 职责:基于滑动窗口策略,在 token 预算内裁剪消息历史
'use strict';
const CONTEXT_CONFIG = {
maxTokens: 200000, // 编号登录用户 200k
maxTokensGuest: 32000, // 访客 32k
systemPromptReserve: 8000, // 系统提示词预留
overflowStrategy: 'sliding-window',
};
/**
* 估算文本 token
* CJK 字符约 1.5 /token拉丁字符约 4 /token
* @param {string} text
* @returns {number}
*/
function estimateTokens(text) {
if (!text) return 0;
const s = String(text);
let cjk = 0, lat = 0;
for (let i = 0; i < s.length; i++) {
const c = s.charCodeAt(i);
if ((c >= 0x4E00 && c <= 0x9FFF) || (c >= 0x3000 && c <= 0x30FF) || (c >= 0xAC00 && c <= 0xD7AF)) {
cjk++;
} else {
lat++;
}
}
return Math.ceil(cjk / 1.5 + lat / 4);
}
/**
* 滑动窗口裁剪消息
* @param {Array} systemMessages - 系统消息数组 [{role:'system', content:'...'}]
* @param {Array} messages - 用户/助手消息历史
* @param {Object} opts
* @param {boolean} opts.isGuest - 是否为访客
* @param {number} opts.contextBudget - 模型路由建议的上下文窗口可覆盖默认值
* @returns {{ messages: Array, trimmed: number, totalTokens: number }}
*/
function trimMessages(systemMessages, messages, opts = {}) {
const { isGuest = false, contextBudget = 0 } = opts;
// 确定 token 上限
let limit;
if (contextBudget > 0) {
limit = contextBudget;
} else {
limit = isGuest ? CONTEXT_CONFIG.maxTokensGuest : CONTEXT_CONFIG.maxTokens;
}
const sysTokens = systemMessages.reduce((n, m) => n + estimateTokens(m.content), 0);
const reserve = Math.max(sysTokens, CONTEXT_CONFIG.systemPromptReserve);
const budget = limit - reserve;
// 从最新消息向前累加,保留预算内的消息
const kept = [];
let used = 0;
for (let i = messages.length - 1; i >= 0; i--) {
const t = estimateTokens(messages[i].content);
if (used + t > budget && kept.length > 0) break;
kept.unshift(messages[i]);
used += t;
}
const trimmed = messages.length - kept.length;
return {
messages: [...systemMessages, ...kept],
trimmed,
totalTokens: reserve + used,
};
}
module.exports = { trimMessages, estimateTokens, CONTEXT_CONFIG };

56
src/brain/index.js Normal file
View File

@ -0,0 +1,56 @@
// src/brain/index.js
// 铸渊核心大脑模块 v3.0
// 职责:统一导出大脑各子系统
//
// 架构:
// prompt-assembler — 系统提示词组装
// mode-detector — 任务模式检测
// model-router — 任务型模型路由
// context-trimmer — 上下文滑动窗口裁剪
// memory-manager — 三层记忆管理
// brain-bridge — 冰朔核心大脑桥(双层互通系统)
'use strict';
const { assemblePrompt, ROLE_MAP, FALLBACK_BRAIN } = require('./prompt-assembler');
const { detectMode, MODES } = require('./mode-detector');
const { selectModel, recordFailure, getRoutingTable, getFailureStatus } = require('./model-router');
const { trimMessages, estimateTokens, CONTEXT_CONFIG } = require('./context-trimmer');
const { generateCandidates, getMemoryStatus, loadLongTermMemory, setTaskMemory, getTaskMemory } = require('./memory-manager');
const bridge = require('./brain-bridge');
const BRAIN_VERSION = 'v3.0';
module.exports = {
BRAIN_VERSION,
// 提示词组装
assemblePrompt,
ROLE_MAP,
FALLBACK_BRAIN,
// 模式检测
detectMode,
MODES,
// 模型路由
selectModel,
recordFailure,
getRoutingTable,
getFailureStatus,
// 上下文裁剪
trimMessages,
estimateTokens,
CONTEXT_CONFIG,
// 记忆管理
generateCandidates,
getMemoryStatus,
loadLongTermMemory,
setTaskMemory,
getTaskMemory,
// 冰朔核心大脑桥
bridge,
};

167
src/brain/memory-manager.js Normal file
View File

@ -0,0 +1,167 @@
// src/brain/memory-manager.js
// 记忆管理器 — 三层记忆架构
// 职责:短期/中期/长期记忆管理,记忆候选生成,写入判定
//
// 记忆分层:
// 短期session当前会话消息存在前端 + 进程内存
// 中期task当前任务状态、开发者进度、决策上下文存在进程内存
// 长期persistent身份信息、项目目标、关键决策写入 brain 文件
'use strict';
const fs = require('fs');
const path = require('path');
// 中期记忆存储(进程内存,重启丢失)
const taskMemory = new Map();
// 记忆候选关键词 — 检测哪些信息值得生成记忆候选
const MEMORY_CANDIDATE_PATTERNS = [
{ type: 'identity_change', pattern: /身份|角色|权限|加入团队|离开|转岗/i, priority: 'high' },
{ type: 'project_goal', pattern: /目标|里程碑|milestone|deadline|截止|规划|roadmap/i, priority: 'high' },
{ type: 'module_ownership', pattern: /负责|接手|模块|m\d{2}-|归属|分工/i, priority: 'medium' },
{ type: 'decision', pattern: /决定|确定|方案|选择|采用|放弃|不再|改为/i, priority: 'high' },
{ type: 'todo', pattern: /待办|todo|下一步|接下来|计划|需要做/i, priority: 'medium' },
{ type: 'bug_fix', pattern: /修复|fix|解决|排查到|根因|原因是/i, priority: 'low' },
{ type: 'deployment', pattern: /上线|部署|发布|deploy|release|版本/i, priority: 'medium' },
];
/**
* 判定规则是否值得写入长期记忆
* 不是什么都记只记高优先级的或中等优先级且重复出现的
*/
const WRITE_RULES = {
high: { minOccurrences: 1, description: '首次出现即记录' },
medium: { minOccurrences: 2, description: '重复提及 2 次后记录' },
low: { minOccurrences: 3, description: '反复提及 3 次后记录' },
};
/**
* 分析文本生成记忆候选
* @param {string} text - 要分析的文本
* @param {string} source - 来源 (user/assistant/system)
* @returns {Array<{type: string, priority: string, excerpt: string, shouldPersist: boolean}>}
*/
function generateCandidates(text, source = 'user') {
if (!text || typeof text !== 'string') return [];
const candidates = [];
for (const { type, pattern, priority } of MEMORY_CANDIDATE_PATTERNS) {
if (pattern.test(text)) {
// 提取匹配上下文的前后50字符作为摘要
const match = text.match(pattern);
const idx = match ? match.index : 0;
const start = Math.max(0, idx - 30);
const end = Math.min(text.length, idx + 80);
const excerpt = text.slice(start, end).replace(/\n/g, ' ').trim();
// 检查中期记忆中的出现次数(使用类型+摘要前40字符作为键降低碰撞风险
const memKey = type + ':' + excerpt.slice(0, 40).replace(/\s+/g, '_');
const taskEntry = taskMemory.get(memKey) || { count: 0 };
taskEntry.count++;
taskEntry.lastSeen = Date.now();
taskMemory.set(memKey, taskEntry);
const rule = WRITE_RULES[priority];
const shouldPersist = taskEntry.count >= rule.minOccurrences;
candidates.push({
type,
priority,
excerpt,
source,
shouldPersist,
occurrences: taskEntry.count,
threshold: rule.minOccurrences,
});
}
}
return candidates;
}
/**
* 保存记忆到中期存储任务级别
* @param {string} sessionId
* @param {string} key
* @param {*} value
*/
function setTaskMemory(sessionId, key, value) {
const sKey = sessionId + ':' + key;
taskMemory.set(sKey, { value, updatedAt: Date.now() });
}
/**
* 读取中期记忆
* @param {string} sessionId
* @param {string} key
* @returns {*}
*/
function getTaskMemory(sessionId, key) {
const entry = taskMemory.get(sessionId + ':' + key);
return entry ? entry.value : undefined;
}
/**
* 清理过期的中期记忆超过 2 小时的条目
*/
function cleanupTaskMemory() {
const cutoff = Date.now() - 2 * 60 * 60 * 1000;
for (const [key, entry] of taskMemory.entries()) {
const ts = entry.updatedAt || entry.lastSeen || 0;
if (ts < cutoff) {
taskMemory.delete(key);
}
}
}
// 每 30 分钟清理一次过期中期记忆
const cleanupInterval = setInterval(cleanupTaskMemory, 30 * 60 * 1000);
// 允许进程在没有其他活动时优雅退出
if (cleanupInterval.unref) cleanupInterval.unref();
/**
* 获取长期记忆 brain 文件读取
* @returns {Object}
*/
function loadLongTermMemory() {
const memoryPath = path.join(__dirname, '../../.github/brain/memory.json');
try {
return JSON.parse(fs.readFileSync(memoryPath, 'utf8'));
} catch (e) {
return null;
}
}
/**
* 获取记忆层状态概览
*/
function getMemoryStatus() {
const longTerm = loadLongTermMemory();
return {
layers: {
short_term: { type: 'session', storage: 'frontend + process', description: '当前会话消息' },
mid_term: { type: 'task', storage: 'process memory', entries: taskMemory.size, description: '当前任务/开发者状态' },
long_term: { type: 'persistent', storage: 'brain files', loaded: !!longTerm, description: '身份/目标/决策/待办' },
},
write_rules: WRITE_RULES,
candidate_types: MEMORY_CANDIDATE_PATTERNS.map(p => ({ type: p.type, priority: p.priority })),
recovery_path: [
'.github/brain/memory.json',
'.github/brain/routing-map.json',
'.github/brain/wake-protocol.md',
'.github/persona-brain/dev-status.json',
],
};
}
module.exports = {
generateCandidates,
setTaskMemory,
getTaskMemory,
cleanupTaskMemory,
loadLongTermMemory,
getMemoryStatus,
MEMORY_CANDIDATE_PATTERNS,
WRITE_RULES,
};

Some files were not shown because too many files have changed in this diff Show More