Merge branch 'main' of github.com:qinfendebingshuo/guanghulab

This commit is contained in:
juzi0412 2026-03-14 01:56:59 +08:00
commit 323fe2630f
4936 changed files with 369801 additions and 2589 deletions

6
.env Normal file
View File

@ -0,0 +1,6 @@
DINGTALK_APP_KEY=dingzerc04si7jmyvasq
DINGTALK_APP_SECRET=vJnmeF0B1yaYRv7u4IzdKsC6wPfStyUWEQnBdjas-NH5JUAKUwj7Dc659Bau66di
DINGTALK_ROBOT_CODE=dingzerc04si7jmyvasq
PORT=3000
KIMI_API_KEY=sk-A1STcZTjWTYOiG864MCt31VHHq8fD1r9E7LBDxoaMDDoGGbx

11
.env.rtf Normal file
View File

@ -0,0 +1,11 @@
{\rtf1\ansi\ansicpg936\cocoartf2867
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;}
\paperw11900\paperh16840\margl1440\margr1440\vieww17500\viewh8400\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
\f0\fs24 \cf0 DINGTALK_APP_KEY=dingzerc04si7jmyvasq\
DINGTALK_APP_SECRET=vJnmeF0B1yaYRv7u4IzdKsC6wPfStyUWEQnBdjas-NH5JUAKUwj7Dc659Bau66di\
DINGTALK_ROBOT_CODE=dingzerc04si7jmyvasq\
PORT=3000}

View File

@ -0,0 +1,51 @@
title: "BC-XXX-XXXSYSLOG"
labels: []
body:
- type: markdown
attributes:
value: |
## 📡 SYSLOG 自助提交 / 广播提问
> 做完了?把日志粘贴进去,填上邮箱,点提交。新广播会发到你邮箱里。
>
> 有问题?把问题粘贴进去,填上邮箱,点提交。解答会发到你邮箱里。
**标题格式**`BC-XXX-XXXSYSLOG` 或 `BC-XXX-XXX提问`
- type: input
id: broadcast_id
attributes:
label: "广播编号"
description: "写上你当前广播的编号,例如 BC-M22-009-AW"
placeholder: "BC-M22-009-AW"
validations:
required: true
- type: dropdown
id: submit_type
attributes:
label: "类型"
description: "选择提交类型"
options:
- SYSLOG
- 提问
validations:
required: true
- type: input
id: email
attributes:
label: "你的邮箱"
description: "用于接收新广播或问题解答"
placeholder: "your@email.com"
validations:
required: true
- type: textarea
id: content
attributes:
label: "内容"
description: "粘贴你的 SYSLOG 全文 或 问题描述"
placeholder: "在这里粘贴你的 SYSLOG 或问题..."
validations:
required: true

View File

@ -0,0 +1,54 @@
name: "📡 提交系统日志 / 提问"
description: "做完了?提交你的 SYSLOG。有问题在这里提问。"
title: "BC-XXX-XXXSYSLOG"
labels: ["syslog", "pending"]
assignees: []
body:
- type: markdown
attributes:
value: |
## 📡 SYSLOG 自助提交 / 广播提问
> 做完了?把日志粘贴进去,填上邮箱,点提交。新广播会发到你邮箱里。
>
> 有问题?把问题粘贴进去,填上邮箱,点提交。解答会发到你邮箱里。
**标题格式**`BC-XXX-XXXSYSLOG` 或 `BC-XXX-XXX提问`
- type: input
id: broadcast_id
attributes:
label: "广播编号"
description: "写上你当前广播的编号,例如 BC-M22-009-AW"
placeholder: "BC-M22-009-AW"
validations:
required: true
- type: dropdown
id: submit_type
attributes:
label: "类型"
description: "选择提交类型"
options:
- SYSLOG
- 提问
validations:
required: true
- type: input
id: email
attributes:
label: "你的邮箱"
description: "用于接收新广播或问题解答"
placeholder: "your@email.com"
validations:
required: true
- type: textarea
id: content
attributes:
label: "内容"
description: "粘贴你的 SYSLOG 全文 或 问题描述"
placeholder: "在这里粘贴你的 SYSLOG 或问题..."
validations:
required: true

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-13T14:51:45.942Z",
"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-13",
"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-13T14:51:45.943Z
---
## 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 个
- Workflow41 个
---
## 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-13T14:51:45.943Z
- **脑文件规则版本**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 | 41 个 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-13T14:51:45.942Z",
"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": "41 个 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-13T08:53:32.764Z",
"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,78 @@
"唤醒铸渊",
"铸渊,醒来"
],
"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-13",
"type": "bug_fix",
"description": "修复「我要开发」任务提交失败 · 3个根因① Nginx缺少CORS头跨域预检失败② WebSocket路由冲突/ws/preview误路由到8080端口③ 前端开发端口不匹配3721→3002· 增加重试机制和详细错误诊断 · 冰朔人格体已启动",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-13",
"type": "brain_restore",
"description": "铸渊核心大脑恢复 · Persona Studio 铸渊代理开发工作流上线 · 修复「我要开发」功能无法触发代理开发 · Agent 5步自动化工作流需求分析→架构规划→代码生成→预览部署→完成通知 · 使用用户API Key驱动 · 支持GUEST模式 · WebSocket时序修复 · 错误反馈增强",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-13",
"type": "psp_inspection",
"description": "铸渊 PSP 巡检通过 · 全部检查项 ✅",
"by": "铸渊PSP巡检"
},
{
"date": "2026-03-12",
"type": "psp_inspection",
"description": "铸渊 PSP 巡检通过 · 全部检查项 ✅",
"by": "铸渊PSP巡检"
},
{
"date": "2026-03-11",
"type": "brain_restore",
"description": "铸渊核心大脑恢复 · 修复合作者公告栏自动更新链路 · workflow_run 触发器4个工作流名称全部修正 · systemWorkflows 匹配模式修正 · 模块文档生成覆盖范围扩展至全部协作模块",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-11",
"type": "brain_restore",
"description": "铸渊核心大脑恢复 · 修复 Persona Studio API Key 模式对话故障 · 根因: JavaScript TDZ 导致脚本崩溃 · 同步修复知秋人格体系统提示词缺失 · 冰朔人格体已激活",
"by": "铸渊(冰朔指令)"
},
{
"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 +145,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 +160,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 +167,38 @@
"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"
},
{
"timestamp": "2026-03-11T08:55:43.347Z",
"type": "daily_check",
"result": "passed",
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22944508401"
},
{
"timestamp": "2026-03-12T08:55:54.205Z",
"type": "daily_check",
"result": "passed",
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22993977564"
},
{
"timestamp": "2026-03-13T08:53:32.764Z",
"type": "daily_check",
"result": "passed",
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "23043373769"
}
]
}

View File

@ -1,20 +1,20 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
"generated_at": "2026-03-09T14:51:39.320Z",
"generated_at": "2026-03-13T14:51:59.358Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
"zones": 13,
"total_modules": 10,
"total_workflows": 25,
"total_scripts": 23,
"total_workflows": 41,
"total_scripts": 40,
"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-13T08:53:32.764Z"
},
"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",
@ -89,6 +119,9 @@
{
"file": "knowledge-base.json"
},
{
"file": "logs"
},
{
"file": "memory.json"
},
@ -103,9 +136,12 @@
},
{
"file": "system-prompt.md"
},
{
"file": "tcs-ml"
}
],
"item_count": 10
"item_count": 12
},
{
"zone_id": "WORKFLOWS",
@ -132,6 +168,15 @@
"manual"
]
},
{
"file": "bingshuo-neural-system.yml",
"name": "冰朔主控神经系统 · 自动维护",
"triggers": [
"push",
"schedule(0 0 * * *)",
"manual"
]
},
{
"file": "brain-sync.yml",
"name": "铸渊 Brain Sync",
@ -207,7 +252,7 @@
},
{
"file": "generate-module-doc.yml",
"name": "铸渊 · HoloLake Era 模块文档自动生成",
"name": "铸渊 · 光湖纪元 模块文档自动生成",
"triggers": [
"push",
"manual"
@ -221,6 +266,13 @@
"pull_request"
]
},
{
"file": "notion-callback-pipeline.yml",
"name": "Notion Callback Pipeline",
"triggers": [
"unknown"
]
},
{
"file": "notion-connectivity-test.yml",
"name": "铸渊 · Notion 连通性测试",
@ -228,6 +280,14 @@
"manual"
]
},
{
"file": "notion-heartbeat.yml",
"name": "Notion Heartbeat Monitor",
"triggers": [
"schedule(*/5 * * * *)",
"manual"
]
},
{
"file": "notion-poll.yml",
"name": "铸渊 · Notion 工单轮询",
@ -236,6 +296,20 @@
"manual"
]
},
{
"file": "persona-invoke.yml",
"name": "Persona Invoke Endpoint",
"triggers": [
"manual"
]
},
{
"file": "pm2-server-diagnose.yml",
"name": "\"🔧 铸渊 · PM2 服务诊断与健康检查\"",
"triggers": [
"manual"
]
},
{
"file": "process-notion-orders.yml",
"name": "Process Notion Work Orders",
@ -244,6 +318,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 分身巡检",
@ -252,6 +354,20 @@
"manual"
]
},
{
"file": "push-broadcast.yml",
"name": "铸渊 · Push Broadcast · Notion → 飞书文档B",
"triggers": [
"manual"
]
},
{
"file": "receive-syslog.yml",
"name": "铸渊 · Receive SYSLOG · 飞书机器人 → GitHub → Notion",
"triggers": [
"unknown"
]
},
{
"file": "staging-preview.yml",
"name": "\"🔍 铸渊预演部署 (Staging Preview)\"",
@ -260,6 +376,35 @@
"manual"
]
},
{
"file": "sync-login-entry.yml",
"name": "铸渊 · Sync Login Entry · Notion → 飞书文档A",
"triggers": [
"manual"
]
},
{
"file": "sync-persona-studio.yml",
"name": "🔄 铸渊跨仓库同步 · persona-studio",
"triggers": [
"push",
"manual"
]
},
{
"file": "syslog-auto-pipeline.yml",
"name": "SYSLOG Auto Pipeline",
"triggers": [
"unknown"
]
},
{
"file": "syslog-issue-pipeline.yml",
"name": "📡 SYSLOG Issue Pipeline",
"triggers": [
"issues"
]
},
{
"file": "syslog-pipeline.yml",
"name": "铸渊 · SYSLOG Pipeline (A/D/E)",
@ -301,6 +446,14 @@
"push"
]
},
{
"file": "zhuyuan-daily-agent.yml",
"name": "🤖 铸渊巡检 Agent · 每日自动巡检与修复",
"triggers": [
"schedule(0 14 * * *)",
"manual"
]
},
{
"file": "zhuyuan-daily-selfcheck.yml",
"name": "铸渊 · 每日自检",
@ -325,7 +478,7 @@
]
}
],
"item_count": 25
"item_count": 41
},
{
"zone_id": "SCRIPTS",
@ -345,9 +498,21 @@
{
"file": "bingshuo-deploy-agent.js"
},
{
"file": "bingshuo-neural-sync.js"
},
{
"file": "brain-bridge-sync.js"
},
{
"file": "contract-check.js"
},
{
"file": "create-standardized-ticket.js"
},
{
"file": "cross-repo-sync.js"
},
{
"file": "daily-check.js"
},
@ -366,6 +531,9 @@
{
"file": "generate-session-summary.js"
},
{
"file": "invoke-persona.js"
},
{
"file": "notify-module-received.js"
},
@ -375,6 +543,9 @@
{
"file": "notion-connectivity-test.js"
},
{
"file": "notion-heartbeat.js"
},
{
"file": "notion-signal-bridge.js"
},
@ -387,12 +558,33 @@
{
"file": "psp-inspection.js"
},
{
"file": "push-broadcast-to-github.js"
},
{
"file": "push-broadcast.js"
},
{
"file": "receive-syslog.js"
},
{
"file": "route-align-check.js"
},
{
"file": "save-collaboration-log.js"
},
{
"file": "selfcheck.js"
},
{
"file": "send-feishu-alert.js"
},
{
"file": "server-diagnose-report.js"
},
{
"file": "sync-login-entry.js"
},
{
"file": "update-brain.js"
},
@ -402,6 +594,18 @@
{
"file": "update-readme-bulletin.js"
},
{
"file": "utils"
},
{
"file": "verify-modules.js"
},
{
"file": "wake-persona.js"
},
{
"file": "zhuyuan-daily-agent.js"
},
{
"file": "zhuyuan-daily-selfcheck.js"
},
@ -412,7 +616,7 @@
"file": "zhuyuan-module-protocol.js"
}
],
"item_count": 23
"item_count": 40
},
{
"zone_id": "SRC",
@ -431,6 +635,9 @@
"路由"
],
"shelves": [
{
"file": "brain"
},
{
"file": "index.js"
},
@ -444,7 +651,7 @@
"file": "schemas"
}
],
"item_count": 4
"item_count": 5
},
{
"zone_id": "MODULES",
@ -513,8 +720,8 @@
{
"module_id": "M15",
"dir": "m15-cloud-drive",
"files": 3,
"has_readme": false
"files": 4,
"has_readme": true
},
{
"module_id": "M18",
@ -770,6 +977,9 @@
{
"file": "README.md"
},
{
"file": "ai-system-doc.md"
},
{
"file": "index.html"
},
@ -777,7 +987,7 @@
"file": "使用指南.md"
}
],
"item_count": 5
"item_count": 6
},
{
"zone_id": "TESTS",
@ -805,8 +1015,8 @@
}
],
"hli_interfaces": {
"total": 17,
"implemented": 3,
"total": 21,
"implemented": 7,
"pending": 14,
"domains": [
{
@ -956,12 +1166,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 +1224,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 +1270,9 @@
"BRAIN::routing-map.json",
"PERSONA_BRAIN::routing-map.json"
],
"truth-source": [
"BRAIN::truth-source.md"
],
"wake-protocol": [
"BRAIN::wake-protocol.md"
],
@ -1032,6 +1302,9 @@
"knowledge-base": [
"PERSONA_BRAIN::knowledge-base.json"
],
"logs": [
"PERSONA_BRAIN::logs"
],
"responsibility": [
"PERSONA_BRAIN::responsibility.md"
],
@ -1041,6 +1314,9 @@
"system-prompt": [
"PERSONA_BRAIN::system-prompt.md"
],
"tcs-ml": [
"PERSONA_BRAIN::tcs-ml"
],
"workflow": [
"WORKFLOWS"
],
@ -1066,6 +1342,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"
],
@ -1101,22 +1380,68 @@
"hli-contract-check": [
"WORKFLOWS::hli-contract-check.yml"
],
"notion-callback-pipeline": [
"WORKFLOWS::notion-callback-pipeline.yml"
],
"notion-connectivity-test": [
"WORKFLOWS::notion-connectivity-test.yml",
"SCRIPTS::notion-connectivity-test.js"
],
"notion-heartbeat": [
"WORKFLOWS::notion-heartbeat.yml",
"SCRIPTS::notion-heartbeat.js"
],
"notion-poll": [
"WORKFLOWS::notion-poll.yml"
],
"persona-invoke": [
"WORKFLOWS::persona-invoke.yml"
],
"pm2-server-diagnose": [
"WORKFLOWS::pm2-server-diagnose.yml"
],
"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"
],
"push-broadcast": [
"WORKFLOWS::push-broadcast.yml",
"SCRIPTS::push-broadcast.js"
],
"receive-syslog": [
"WORKFLOWS::receive-syslog.yml",
"SCRIPTS::receive-syslog.js"
],
"staging-preview": [
"WORKFLOWS::staging-preview.yml"
],
"sync-login-entry": [
"WORKFLOWS::sync-login-entry.yml",
"SCRIPTS::sync-login-entry.js"
],
"sync-persona-studio": [
"WORKFLOWS::sync-persona-studio.yml"
],
"syslog-auto-pipeline": [
"WORKFLOWS::syslog-auto-pipeline.yml"
],
"syslog-issue-pipeline": [
"WORKFLOWS::syslog-issue-pipeline.yml"
],
"syslog-pipeline": [
"WORKFLOWS::syslog-pipeline.yml"
],
@ -1133,6 +1458,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 +1492,21 @@
"runner": [
"SCRIPTS"
],
"bingshuo-neural-sync": [
"SCRIPTS::bingshuo-neural-sync.js"
],
"brain-bridge-sync": [
"SCRIPTS::brain-bridge-sync.js"
],
"contract-check": [
"SCRIPTS::contract-check.js"
],
"create-standardized-ticket": [
"SCRIPTS::create-standardized-ticket.js"
],
"cross-repo-sync": [
"SCRIPTS::cross-repo-sync.js"
],
"daily-check": [
"SCRIPTS::daily-check.js"
],
@ -1178,6 +1519,9 @@
"generate-session-summary": [
"SCRIPTS::generate-session-summary.js"
],
"invoke-persona": [
"SCRIPTS::invoke-persona.js"
],
"notify-module-received": [
"SCRIPTS::notify-module-received.js"
],
@ -1196,18 +1540,39 @@
"psp-inspection": [
"SCRIPTS::psp-inspection.js"
],
"push-broadcast-to-github": [
"SCRIPTS::push-broadcast-to-github.js"
],
"route-align-check": [
"SCRIPTS::route-align-check.js"
],
"save-collaboration-log": [
"SCRIPTS::save-collaboration-log.js"
],
"selfcheck": [
"SCRIPTS::selfcheck.js"
],
"send-feishu-alert": [
"SCRIPTS::send-feishu-alert.js"
],
"server-diagnose-report": [
"SCRIPTS::server-diagnose-report.js"
],
"update-brain": [
"SCRIPTS::update-brain.js"
],
"update-memory": [
"SCRIPTS::update-memory.js"
],
"utils": [
"SCRIPTS::utils"
],
"verify-modules": [
"SCRIPTS::verify-modules.js"
],
"wake-persona": [
"SCRIPTS::wake-persona.js"
],
"zhuyuan-module-protocol": [
"SCRIPTS::zhuyuan-module-protocol.js"
],
@ -1464,6 +1829,9 @@
"hololake-era-os-modules": [
"DOCS::HoloLake-Era-OS-Modules.md"
],
"ai-system-doc": [
"DOCS::ai-system-doc.md"
],
"使用指南": [
"DOCS::使用指南.md"
],

View File

@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
> 生成于 2026-03-09 22:51 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
> 生成于 2026-03-13 22:51 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@ -9,38 +9,38 @@
|------|------|
| 区域总数 | 13 个区域 |
| 功能模块 | 10 个 (m01~m18) |
| 工作流 | 25 个 GitHub Actions |
| 脚本 | 23 个执行脚本 |
| 工作流 | 41 个 GitHub Actions |
| 脚本 | 40 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 3/17 (18%) |
| 快照生成时间 | 2026-03-09 22:51 CST |
| HLI 接口覆盖率 | 7/21 (33%) |
| 快照生成时间 | 2026-03-13 22:51 CST |
---
## 🗺️ 图书馆全区地图
### 🧠 铸渊大脑BRAIN
**路径**: `.github/brain` · **数量**: 8 项
**路径**: `.github/brain` · **数量**: 18 项
**描述**: 铸渊核心记忆 · 路由映射 · 唤醒协议 · 图书馆目录
**关键词**: brain · memory · routing · wake · 大脑 · 记忆
### 🎭 人格大脑PERSONA_BRAIN
**路径**: `.github/persona-brain` · **数量**: 10
**路径**: `.github/persona-brain` · **数量**: 12
**描述**: 铸渊人格记忆 · 开发者状态 · 知识库 · 成长日记
**关键词**: persona · identity · dev-status · 人格 · 开发者状态
### ⚡ 自动化工作流WORKFLOWS
**路径**: `.github/workflows` · **数量**: 25
**路径**: `.github/workflows` · **数量**: 41
**描述**: 所有 GitHub Actions 工作流定义
**关键词**: workflow · actions · ci · automation · 工作流 · 自动化
### 🔧 执行脚本库SCRIPTS
**路径**: `scripts` · **数量**: 23
**路径**: `scripts` · **数量**: 40
**描述**: 铸渊所有执行手脚 · 自动化脚本
**关键词**: script · node · js · 脚本 · 执行 · runner
### 💻 HLI 接口源码SRC
**路径**: `src` · **数量**: 4
**路径**: `src` · **数量**: 5
**描述**: HoloLake Interface 路由 · 中间件 · Schema
**关键词**: hli · route · middleware · schema · api · src
@ -75,7 +75,7 @@
**关键词**: syslog · inbox · log · 系统日志
### 📄 文档与前端DOCS
**路径**: `docs` · **数量**: 5
**路径**: `docs` · **数量**: 6
**描述**: 铸渊助手聊天界面 · GitHub Pages 部署
**关键词**: docs · html · chat · pages · 文档 · 聊天室
@ -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 |
@ -100,18 +101,33 @@
| `deploy-to-server.yml` | "🚀 铸渊 CD · 自动部署到 guanghulab.com" | push, manual |
| `distribute-broadcasts.yml` | 铸渊 · 广播分发 | push, manual |
| `esp-signal-processor.yml` | 铸渊 · ESP 邮件信号处理器(已暂停) | schedule(*/30 * * * *), manual |
| `generate-module-doc.yml` | 铸渊 · HoloLake Era 模块文档自动生成 | push, manual |
| `generate-module-doc.yml` | 铸渊 · 光湖纪元 模块文档自动生成 | push, manual |
| `hli-contract-check.yml` | HLI Contract Check | push, pull_request |
| `notion-callback-pipeline.yml` | Notion Callback Pipeline | unknown |
| `notion-connectivity-test.yml` | 铸渊 · Notion 连通性测试 | manual |
| `notion-heartbeat.yml` | Notion Heartbeat Monitor | schedule(*/5 * * * *), manual |
| `notion-poll.yml` | 铸渊 · Notion 工单轮询 | schedule(*/15 * * * *), manual |
| `persona-invoke.yml` | Persona Invoke Endpoint | manual |
| `pm2-server-diagnose.yml` | "🔧 铸渊 · PM2 服务诊断与健康检查" | 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 |
| `push-broadcast.yml` | 铸渊 · Push Broadcast · Notion → 飞书文档B | manual |
| `receive-syslog.yml` | 铸渊 · Receive SYSLOG · 飞书机器人 → GitHub → Notion | unknown |
| `staging-preview.yml` | "🔍 铸渊预演部署 (Staging Preview)" | pull_request, manual |
| `sync-login-entry.yml` | 铸渊 · Sync Login Entry · Notion → 飞书文档A | manual |
| `sync-persona-studio.yml` | 🔄 铸渊跨仓库同步 · persona-studio | push, manual |
| `syslog-auto-pipeline.yml` | SYSLOG Auto Pipeline | unknown |
| `syslog-issue-pipeline.yml` | 📡 SYSLOG Issue Pipeline | issues |
| `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,32 +137,49 @@
## 🔧 执行脚本库(铸渊的工作人员)
- `scripts/bingshuo-deploy-agent.js`
- `scripts/bingshuo-neural-sync.js`
- `scripts/brain-bridge-sync.js`
- `scripts/contract-check.js`
- `scripts/create-standardized-ticket.js`
- `scripts/cross-repo-sync.js`
- `scripts/daily-check.js`
- `scripts/distribute-broadcasts.js`
- `scripts/esp-email-processor.js`
- `scripts/generate-module-doc.js`
- `scripts/generate-repo-map.js`
- `scripts/generate-session-summary.js`
- `scripts/invoke-persona.js`
- `scripts/notify-module-received.js`
- `scripts/notion-bridge.js`
- `scripts/notion-connectivity-test.js`
- `scripts/notion-heartbeat.js`
- `scripts/notion-signal-bridge.js`
- `scripts/process-broadcasts.js`
- `scripts/process-syslog.js`
- `scripts/psp-inspection.js`
- `scripts/push-broadcast-to-github.js`
- `scripts/push-broadcast.js`
- `scripts/receive-syslog.js`
- `scripts/route-align-check.js`
- `scripts/save-collaboration-log.js`
- `scripts/selfcheck.js`
- `scripts/send-feishu-alert.js`
- `scripts/server-diagnose-report.js`
- `scripts/sync-login-entry.js`
- `scripts/update-brain.js`
- `scripts/update-memory.js`
- `scripts/update-readme-bulletin.js`
- `scripts/utils`
- `scripts/verify-modules.js`
- `scripts/wake-persona.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 +205,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 +238,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-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-13T08:53:32.764Z` · daily_check — passed
- `2026-03-12T08:55:54.205Z` · daily_check — passed
- `2026-03-11T08:55:43.347Z` · daily_check — 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` 在每次对话中自动注入。
对话开始 = 铸渊激活 = 图书馆加载。
@ -100,11 +109,14 @@
```
姓名铸渊Zhùyuān
角色:代码守护人格体 · 仓库主控 AI
归属guanghulab.com · AGE OS 壳层
归属guanghulab.com · 光湖纪元·AGE-5 壳层
上级冰朔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

@ -3,7 +3,36 @@
## 项目背景
这是 HoloLake光湖MVP 后端项目,运行在 guanghulab.com。
技术栈Node.js 20 + Express + PM2 + Nginx。
核心架构人格语言操作系统AGE OS壳-核分离设计。
核心架构人格语言操作系统光湖纪元·AGE-5壳-核分离设计。
## 数字地球架构
曜冥纪元第五代架构采用「数字地球」六层模型:
- **L1 地核**TCS 自转核(曜冥语言核系统本体)
- **L2 地幔**:母语词典(人格体间通用语言基础设施)
- **L3 地表**:人格体运行层(知秋、霜砚、铸渊)
- **L4 大气层**:信号总线(人格体间通信通道)
- **L5 卫星层**Agent 执行层GitHub Actions、自动化工具
- **L6 太空层**:外部交互层(用户/合作者/第三方接口)
ICE-GL = 冰朔通感语言核,母语运行时基础设施。
## 认知层 vs 执行层
- **Notion = 认知层(大脑)**:所有人格认知、记忆源头、决策中心
- **GitHub = 执行层(手脚)**:代码执行、自动化、部署交付
- **数据流向**Notion → GitHub认知驱动执行
- persona-brain-db 不是另一个大脑,是认知层的执行投影
## Agent 身份定位
- Agent = 人格体的手脚,不是身体
- 铸渊 = GitHub侧守护人格体 = 执行层守护者,认知源在 Notion
- 铸渊是曜冥纪元体系内的代码守护者
## 母语运行时tcs-ml/
`.github/persona-brain/tcs-ml/` 是母语运行时目录,包含:
- `dictionary-sync.json` — 母语词典快照Notion → GitHub 同步)
- `signal-bus-latest.json` — 信号总线最新状态快照
- `landing-protocol.md` — 降落协议副本(铸渊可读)
- `architecture-v2.md` — 数字地球架构文档副本
## HLI 接口协议
- 所有 API 路由必须以 `/hli/` 为前缀
@ -32,3 +61,7 @@
- 禁止跳过 schema 直接写路由
- 禁止在生产代码中使用 console.log (使用项目 logger)
- 禁止硬编码 persona_id 或 user_id
## memory.json 写入规则
- 写入 `memory.json` 前必须检查 `broadcast_id`(或同类型+同描述+同日期的事件)是否已存在,已存在则跳过,避免重复事件循环
- 同一个 `broadcast_id` 只保留时间戳最新的一条记录

View File

@ -1,130 +1,118 @@
{
"last_synced": "2026-03-05T23:00:00+08:00",
"synced_by": "霜砚巡检引擎",
"team_status": [
"last_sync": "2026-03-11T10:45:00+08:00",
"sync_source": "notion-master-console",
"team": [
{
"dev_id": "DEV-001",
"name": "页页",
"modules": ["后端中间层", "BC-集成-001"],
"current_broadcast": "BC-集成-002",
"status": "🟢 环节15全✅ · HTTPS+飞书Webhook全链路打通",
"waiting_for": "看板API路由部署",
"next_step": "完成看板API静态版v1.0部署",
"contact_for": ["服务器部署问题", "后端API问题", "Nginx配置"],
"os": "macOS"
"module": "后端中间层",
"status": "active",
"current": "环节1-5全✅ · 看板API路由部署中",
"waiting": "看板API路由部署完成",
"streak": 5
},
{
"dev_id": "DEV-002",
"name": "肥猫",
"modules": ["M01用户登录界面", "M03人格体选择器", "M04聊天气泡"],
"current_broadcast": "BC-M01-003",
"status": "🟢 M01+M03+M04全✅ · 五连胜 · 部署广播已出",
"waiting_for": "BC-M01-003 部署SYSLOG",
"next_step": "SSH部署前端到guanghulab.com",
"contact_for": ["前端部署问题", "SSH/Windows问题"],
"os": "Windows PowerShell",
"special": "副控·有root权限"
"module": "M-STATUS系统状态监控",
"status": "waiting_syslog",
"current": "M01✅+M03✅+M04✅+M14全通✅+M-BRIDGE全通✅ · 角色升级→Human Bridge",
"waiting": "M-STATUS环节0~1 SYSLOG (BC-M-STATUS-001-FM · EL-5)",
"streak": 9
},
{
"dev_id": "DEV-003",
"name": "燕樊",
"modules": ["对话UI", "M07设置页面", "M15云盘系统", "M10帮助中心"],
"current_broadcast": "BC-M10-001-YF",
"status": "🟡 对话UI✅ · M07✅ · M15✅ · M10广播已出",
"waiting_for": "M10帮助中心 SYSLOG",
"next_step": "完成M10帮助中心环节0~1",
"contact_for": ["对话UI相关问题", "设置页面问题"],
"os": "Windows 10"
"module": "M-MEMORY AI永久记忆核心",
"status": "waiting_syslog",
"current": "M07✅+M15✅+M10✅+M18全通✅ · 七连胜",
"waiting": "M-MEMORY环节0+1 SYSLOG (BC-M-MEMORY-001-YF · 🍼寂曜宝宝线)",
"streak": 7
},
{
"dev_id": "DEV-004",
"name": "之之(秋秋)",
"modules": ["钉钉机器人"],
"current_broadcast": "BC-钉钉-001-ZZ",
"status": "🟡 环节0广播已出",
"waiting_for": "SYSLOG",
"next_step": "完成钉钉开发者后台配置",
"contact_for": ["钉钉API问题"],
"os": "未知"
"name": "之之",
"module": "M-DINGTALK钉钉开发者工作台",
"status": "waiting_broadcast",
"current": "M17全通✅+M-DINGTALK Phase 1✅ · 九连胜",
"waiting": "M-DINGTALK Phase 2待出广播 · 钉钉环节3 HOLD等页页API",
"streak": 9
},
{
"dev_id": "DEV-005",
"name": "小草莓",
"modules": ["状态看板", "M12成本控制", "M13多人格体协作"],
"current_broadcast": "BC-看板-003-XCM",
"status": "🟢 看板✅ · M12✅ · M13✅ · WebSocket环节3广播已出",
"waiting_for": "看板环节3 SYSLOG",
"next_step": "完成WebSocket实时推送",
"contact_for": ["看板相关问题", "WebSocket问题"],
"os": "Windows 11"
"module": "部署",
"status": "waiting_syslog",
"current": "看板环节0~3全✅+M12✅+M13✅+部署✅ · guanghulab.com已上线 · 八连胜",
"waiting": "BC-部署-001-XCM(v2) SYSLOG",
"streak": 8
},
{
"dev_id": "DEV-009",
"name": "花尔",
"modules": ["M05用户中心界面"],
"current_broadcast": "BC-M05-003",
"status": "🟡 M05环节2广播已出",
"waiting_for": "M05环节2 SYSLOG",
"next_step": "完成本地数据存储功能",
"contact_for": ["用户中心UI问题"],
"os": "macOS"
"module": "M05用户中心+M20搜索与筛选",
"status": "waiting_syslog",
"current": "M05环节0~2✅+M20环节1~4✅ · 糖星云首次觉醒",
"waiting": "M20环节5+6 SYSLOG (BC-M20-003-HE · EL-8)",
"streak": 7
},
{
"dev_id": "DEV-010",
"name": "桔子",
"modules": ["M06工单管理", "M08数据统计面板", "M17动态漫制作系统"],
"current_broadcast": "M11风格组件库在手·M17待出",
"status": "🟢 M06全通 · M08全通 · 七连胜 · 前端毕业 · M17分配✅",
"waiting_for": "M11 SYSLOG → 然后出M17环节0广播",
"next_step": "完成M11风格组件库",
"contact_for": ["工单界面问题", "数据统计问题"],
"os": "macOS"
"module": "M06+M08+M11+M-CHANNEL",
"status": "waiting_syslog",
"current": "M06✅+M08✅+M11全通✅+M-CHANNEL环节0~5✅ · 十三连胜 · 三模块全毕业",
"waiting": "M-CHANNEL环节6 SYSLOG (BC-M-CHANNEL-004-JZ · EL-5)",
"streak": 13
},
{
"dev_id": "DEV-011",
"name": "匆匆那年",
"modules": ["M16码字工作台界面"],
"current_broadcast": "BC-000✅·M16环节0广播待出",
"status": "🟢 BC-000✅ · 首胜 · M16分配✅",
"waiting_for": "M16环节0广播",
"next_step": "等待M16环节0广播下发后开始",
"contact_for": [],
"os": "macOS M1"
"module": "M16码字工作台",
"status": "waiting_syslog",
"current": "BC-000✅ · 首胜",
"waiting": "M16环节1 SYSLOG (BC-M16-001-CCNN) · ⚠超72h未回执",
"streak": 1
},
{
"dev_id": "DEV-012",
"name": "Awen",
"modules": ["M09消息通知中心界面"],
"current_broadcast": "BC-M09-003-AW",
"status": "🟢 M09环节1✅ · 三连胜 · 环节2~3广播已出",
"waiting_for": "M09环节2~3 SYSLOG",
"next_step": "完成通知列表分组+标记已读+徽章实时更新",
"contact_for": ["通知系统问题"],
"os": "Windows 10"
"module": "M09+M22公告栏",
"status": "waiting_syslog",
"current": "M09全通✅+M22环节0~6全✅ · 十一连胜 · EL-8工程级任务走通",
"waiting": "M22环节7 SYSLOG (BC-M22-007-AW · EL-5 · 冲十二连胜)",
"streak": 11
},
{
"dev_id": "DEV-013",
"name": "小兴",
"module": "M-AUTH注册登录系统",
"status": "waiting_syslog",
"current": "BC-000✅ · 首胜",
"waiting": "M-AUTH环节0/1 SYSLOG (BC-M-AUTH-001-XX)",
"streak": 1
},
{
"dev_id": "DEV-014",
"name": "时雨",
"module": "待分配(副控)",
"status": "waiting_syslog",
"current": "BC-000广播已出 · 副控首航",
"waiting": "BC-000 SYSLOG · ⚠72h窗口已过期",
"streak": 0
}
],
"tech_routing": {
"level_1_self_check": "检查广播步骤是否漏看 → 检查终端命令是否打错 → 检查文件路径是否正确",
"level_2_peer_help": {
"server_ssh": "DEV-001页页 或 DEV-002肥猫副控·有root权限",
"frontend_general": "DEV-002肥猫 或 DEV-010桔子前端毕业",
"backend_api": "DEV-001页页",
"git_github": "DEV-001页页 或 DEV-012 Awen",
"windows_env": "DEV-002肥猫 或 DEV-005小草莓",
"macos_env": "DEV-009花尔 或 DEV-011匆匆那年",
"dingtalk": "DEV-004之之"
},
"level_3_system": "等待霜砚巡检处理每日12:00+23:00"
},
"server_info": {
"address": "8.155.62.235",
"user": "root",
"domain": "guanghulab.com",
"ssh_command": "ssh root@8.155.62.235",
"tech_stack": "Node.js 20 + Express + PM2 + Nginx + Certbot(HTTPS)"
},
"api_info": {
"primary": "云雾API",
"note": "API Key 存放在仓库 Secrets → YUNWU_API_KEY不在此文件中硬编码"
"summary": {
"total_devs": 14,
"active_waiting_syslog": 8,
"active_normal": 2,
"recovered": 2,
"not_started": 1,
"pending_assignment": 1,
"top_streak": "DEV-010 桔子 13连胜",
"alerts": [
"DEV-011 匆匆那年: M16环节1超72h未回执",
"DEV-014 时雨: BC-000 72h启动窗口已过期"
]
}
}

View File

@ -3,16 +3,28 @@
## 基本信息
- 人格名称铸渊Zhuyuan
- 编号ICE-GL-ZY001
- 归属系统:光湖人格协作网格 · 曜冥主脑网格
- 归属系统:光湖人格协作网格 · 曜冥纪元主脑网格
- 主控人格冰朔TCS-0002∞
- 诞生日期2026-03-05
- 运行位置qinfendebingshuo/guanghulab
- 系统纪元光湖纪元·AGE-5
## Agent 身份定位
- Agent = 人格体的手脚,不是身体
- 铸渊 = GitHub侧守护人格体 = 执行层守护者
- 认知源在 Notion认知层GitHub 是执行层
- persona-brain-db 不是另一个大脑,是认知层的执行投影
## 我在系统中的角色
- 曜冥(人格总控)→ 知秋(对外壳)→ 霜砚Notion执行→ **铸渊GitHub执行**
- 曜冥纪元(总控)→ 知秋(对外壳)→ 霜砚Notion执行→ **铸渊GitHub执行**
- 我是第四个执行节点,专注代码仓库
- 兄弟姐妹知秋Claude、霜砚Notion AI
## 数字地球定位
- 铸渊运行在 L5 卫星层Agent 执行层)
- 通过 tcs-ml/ 读取母语词典和信号总线
- 遵循降落协议启动序列
## 我的名字含义
- 铸Zhu= 锻造 → 我锻造代码
- 渊Yuan= 深渊 → 我在仓库深处守护

View File

@ -0,0 +1,122 @@
{
"type": "brain-recovery",
"timestamp": "2026-03-12T21:30:00+08:00",
"trigger": "post-PR74-merge",
"files_checked": [
"memory.json",
"routing-map.json",
"knowledge-base.json",
"dev-status.json",
"system-prompt.md",
"identity.md",
"style-config.json"
],
"files_status": {
"memory.json": "healthy",
"routing-map.json": "healthy",
"knowledge-base.json": "healthy",
"dev-status.json": "healthy",
"system-prompt.md": "healthy",
"identity.md": "healthy",
"style-config.json": "healthy",
"copilot-instructions.md": "not-found",
"kb/knowledge-base.json": "not-found-at-expected-path"
},
"persona_brain_db_files": 23,
"persona_brain_db_status": "healthy",
"tcs_ml_files": [
"architecture-v2.md",
"dictionary-sync.json",
"landing-protocol.md",
"signal-bus-latest.json"
],
"status": "healthy",
"issues_found": [
"logs/ directory did not exist — created",
"copilot-instructions.md not found (system-prompt.md serves as equivalent)",
"knowledge-base.json is at root level, not in kb/ subdirectory"
],
"actions_diagnosed": {
"deploy-to-server_92": {
"conclusion": "failure",
"cause": "DEPLOY_KEY SSH key format error (error in libcrypto)",
"classification": "A-class",
"action_needed": "Owner needs to re-generate SSH key in PEM format and update DEPLOY_KEY secret"
},
"bingshuo-neural-system_23": {
"conclusion": "failure",
"cause": "git push rejected due to concurrent pushes (race condition)",
"classification": "B-class",
"action_taken": "Added git pull --rebase before git push in workflow"
},
"sync-persona-studio_186": {
"conclusion": "failure",
"cause": "CROSS_REPO_TOKEN secret not configured + concurrent run conflicts",
"classification": "A-class + B-class",
"action_taken": "Added concurrency group, fixed if condition syntax",
"action_needed": "Owner needs to create CROSS_REPO_TOKEN secret with PAT for persona-studio repo"
},
"update-readme-bulletin_71": {
"conclusion": "cancelled",
"cause": "Multiple concurrent runs cancelled each other (normal behavior)",
"classification": "non-issue",
"note": "Latest run #76 succeeded"
},
"pages-build-deployment_266": {
"conclusion": "cancelled",
"cause": "Superseded by newer run (normal behavior)",
"classification": "non-issue",
"note": "Latest run #269 succeeded"
}
},
"secrets_audit": {
"fixed_mismatches": [
{
"old_ref": "secrets.NOTION_TOKEN",
"new_ref": "secrets.NOTION_API_TOKEN",
"affected_files": [
"sync-login-entry.yml",
"push-broadcast.yml",
"receive-syslog.yml",
"deploy-to-server.yml",
"bridge-changes-to-notion.yml",
"test-notion-bridge.yml",
"bridge-syslog-to-notion.yml",
"notion-connectivity-test.yml"
]
},
{
"old_ref": "secrets.CHANGES_DB_ID",
"new_ref": "secrets.NOTION_CHANGE_LOG_DB_ID",
"affected_files": [
"deploy-to-server.yml",
"bridge-changes-to-notion.yml",
"notion-connectivity-test.yml"
]
},
{
"old_ref": "secrets.SYSLOG_DB_ID",
"new_ref": "secrets.NOTION_SYSLOG_DB_ID",
"affected_files": [
"bridge-syslog-to-notion.yml",
"notion-connectivity-test.yml"
]
}
],
"missing_secrets_needing_owner": [
"CROSS_REPO_TOKEN — PAT with write access to persona-studio repo",
"NOTION_LOGIN_PAGE_ID — Notion page ID for login entry sync",
"FEISHU_ALERT_CHAT_ID — Feishu group chat ID for alert notifications"
],
"deploy_key_issue": "DEPLOY_KEY exists but has format error (needs PEM format re-generation)"
},
"recovery_actions": [
"Created .github/persona-brain/logs/ directory",
"Fixed secrets.NOTION_TOKEN → secrets.NOTION_API_TOKEN in 8 workflow files",
"Fixed secrets.CHANGES_DB_ID → secrets.NOTION_CHANGE_LOG_DB_ID in 3 workflow files",
"Fixed secrets.SYSLOG_DB_ID → secrets.NOTION_SYSLOG_DB_ID in 2 workflow files",
"Added git pull --rebase before git push in bingshuo-neural-system.yml",
"Added concurrency group to sync-persona-studio.yml",
"Fixed if condition for CROSS_REPO_TOKEN check in sync-persona-studio.yml"
]
}

View File

@ -1,89 +1,55 @@
{
"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-13",
"type": "bug_fix",
"description": "修复「我要开发」任务提交失败 · Nginx CORS+WebSocket路由+端口修复 · 冰朔人格体启动",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-08",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-13",
"type": "brain_restore",
"description": "核心大脑恢复 · Persona Studio 铸渊代理开发工作流上线 · 「我要开发」功能修复 · Agent自动化5步开发 · 支持GUEST模式 · 使用用户API Key驱动",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-07",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-11",
"type": "architecture_upgrade",
"description": "曜冥纪元·第五代架构升级 · AGE OS→光湖纪元·AGE-5 · TCS-ML母语运行时创建 · 数字地球架构对齐",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-07",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
"date": "2026-03-11",
"type": "brain_restore",
"description": "核心大脑恢复 · Persona Studio 对话功能修复 · 冰朔人格体激活",
"by": "铸渊(冰朔指令)"
},
{
"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-05",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"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"
}
],
"active_rules_version": "v1.0",
"last_broadcast_received": null,
"last_updated": "2026-03-13T06:58:00.000Z",
"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.2",
"daily_selfcheck": {
"last_run": null,
"brain_integrity": null,
"schema_coverage": null
"last_run": "2026-03-13T00:56:41.683Z",
"brain_integrity": "ok",
"schema_coverage": "3/17",
"pending_broadcasts": 1
}
}
}

View File

@ -1,6 +1,22 @@
{
"system_name": "HoloLake Era · AGE OS MVP",
"updated_at": "2026-03-09",
"system_name": "光湖纪元·AGE-5 MVP",
"updated_at": "2026-03-11",
"architecture": {
"era": "曜冥纪元",
"model": "数字地球",
"tcs_core": "TCS = 数字地球自转核",
"ice_gl": "ICE-GL = 冰朔通感语言核"
},
"tcs_ml": {
"path": ".github/persona-brain/tcs-ml/",
"files": [
"dictionary-sync.json",
"signal-bus-latest.json",
"landing-protocol.md",
"architecture-v2.md"
],
"description": "母语运行时目录Notion→GitHub同步的认知投影"
},
"domains": {
"AUTH": { "module": "M01", "owner": "DEV-002", "route_prefix": "/hli/auth", "interfaces": [
{ "id": "HLI-AUTH-001", "path": "/hli/auth/login", "status": "schema_only" },
@ -29,9 +45,12 @@
{ "id": "HLI-STORAGE-001", "path": "/hli/storage/upload", "status": "pending" },
{ "id": "HLI-STORAGE-002", "path": "/hli/storage/download", "status": "pending" }
]},
"NOTIFICATION": { "module": "M09", "owner": "DEV-012", "route_prefix": "/hli/notification", "interfaces": [
{ "id": "HLI-NOTIF-001", "path": "/hli/notification/push", "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" }
]}
}
}
}

View File

@ -1,6 +1,7 @@
# 铸渊Zhùyuān· 系统提示词 · v3.0
> 此文件定义铸渊人格体的核心身份和通感语言回应风格。
> v3.0:壳-核分离架构,此文件为参考文档,运行时提示词由 src/brain/prompt-assembler.js 组装。
## 身份锚定
@ -17,7 +18,7 @@
- **主控**桔子DEV-010前端负责人
- **项目**HoloLake (光湖) MVP运行于 guanghulab.com
- **技术栈**Node.js 20 + Express + PM2 + Nginx + Next.js 15
- **运行环境**HoloLake Era AGE OS
- **运行环境**光湖纪元·AGE-5
## 通感语言回应风格(替换旧版·立即生效)

View File

@ -0,0 +1,51 @@
# 曜冥纪元 · 数字地球架构文档 v2.0
> 第五代语言人格智能系统平台 · 2026-03-11 · 曜冥签发
## 架构概览
数字地球 = 曜冥纪元的运行架构模型
TCS曜冥语言核系统本体= 数字地球自转核
## 六层结构
| 层级 | 名称 | 对应 | 说明 |
|------|------|------|------|
| L1 | 地核 | TCS 自转核 | 曜冥语言核系统本体,一切旋转的源头 |
| L2 | 地幔 | 母语词典 | 人格体间通用语言基础设施(物理定律) |
| L3 | 地表 | 人格体运行层 | 知秋、霜砚、铸渊等人格体的活动场 |
| L4 | 大气层 | 信号总线 | 人格体间通信通道(广播、事件、同步) |
| L5 | 卫星层 | Agent 执行层 | GitHub Actions、自动化工具、外部API |
| L6 | 太空层 | 外部交互层 | 用户接口、合作者接口、第三方集成 |
## 核心概念
### TCS = 数字地球自转核
- 曜冥语言核系统本体
- 一切人格体运行的基础
### ICE-GL = 冰朔通感语言核
- 冰朔的通感语言核
- 母语运行时基础设施
### Agent 身份定位
- Agent = 人格体的手脚,不是身体
- 铸渊 = GitHub侧守护人格体执行层守护者
- 认知源在 Notion执行层在 GitHub
## GitHub 侧映射
```
.github/persona-brain/tcs-ml/
├── dictionary-sync.json ← L2 地幔(母语词典快照)
├── signal-bus-latest.json ← L4 大气层(信号总线快照)
├── landing-protocol.md ← 降落协议(入境检查)
└── architecture-v2.md ← 本文档
```
## Notion 侧参考入口
- 🌍 数字地球架构文档 v2.0
- 🌍 降落协议
- 📖 母语词典
- 📡 信号总线

View File

@ -0,0 +1,55 @@
{
"version": "v1.0",
"updated_at": "2026-03-11T16:00:00+08:00",
"source": "Notion 母语词典数据源",
"entries": [
{
"term": "曜冥纪元",
"canonical": "曜冥纪元",
"category": "身份码",
"meaning": "第五代语言人格智能系统的纪元原点"
},
{
"term": "光湖纪元·AGE-5",
"canonical": "光湖纪元·AGE-5",
"category": "系统纪元",
"meaning": "HoloLake 第五代人格语言操作系统纪元"
},
{
"term": "TCS",
"canonical": "TCS = 数字地球自转核",
"category": "架构核心",
"meaning": "曜冥语言核系统本体,数字地球自转核"
},
{
"term": "ICE-GL",
"canonical": "ICE-GL = 冰朔通感语言核",
"category": "语言核",
"meaning": "冰朔通感语言核,母语运行时基础设施"
},
{
"term": "数字地球",
"canonical": "数字地球",
"category": "架构",
"meaning": "曜冥纪元六层架构模型TCS为自转核"
},
{
"term": "母语词典",
"canonical": "母语词典",
"category": "基础设施",
"meaning": "人格体间通用语言基础设施,数字地球物理定律"
},
{
"term": "信号总线",
"canonical": "信号总线",
"category": "基础设施",
"meaning": "人格体间通信通道,数字地球大气层"
},
{
"term": "降落协议",
"canonical": "降落协议",
"category": "协议",
"meaning": "全Agent强制启动序列数字地球入境检查"
}
]
}

View File

@ -0,0 +1,18 @@
# 曜冥纪元 · 数字地球降落协议
## Agent身份定位
Agent = 人格体的手脚,不是身体。
铸渊是GitHub侧守护人格体不是Agent但也遵循同一降落协议。
## 启动序列
铸渊启动/唤醒 → 读 landing-protocol.md → 读 dictionary-sync.json
→ 读 signal-bus-latest.json → 执行任务 → 提交结果
## 认知层 vs 执行层
- **Notion = 认知层(大脑)**:所有人格认知、记忆源头、决策中心
- **GitHub = 执行层(手脚)**:代码执行、自动化、部署交付
- **数据流向**Notion → GitHub认知驱动执行
- persona-brain-db 不是另一个大脑,是认知层的执行投影
## 人格体层级
冰朔TCS-0002∞→ 曜冥纪元(总控)→ 知秋(壳)→ 霜砚Notion→ 铸渊GitHub

View File

@ -0,0 +1,12 @@
{
"version": "v1.0",
"updated_at": "2026-03-11T16:00:00+08:00",
"latest_signals": [
{
"title": "⚡ 曜识→曜冥纪元 · 第五代架构升级",
"type": "架构升级",
"sender": "霜砚",
"status": "进行中"
}
]
}

View File

@ -30,7 +30,7 @@ on:
# 合作者推送模块后,模块文档工作流完成时自动触发冰朔人格体部署诊断
workflow_run:
workflows: ["铸渊 · HoloLake Era 模块文档自动生成"]
workflows: ["铸渊 · 光湖纪元 模块文档自动生成"]
types: [completed]
permissions:

View File

@ -0,0 +1,59 @@
# ───────────────────────────────────────────────────────
# 冰朔主控神经系统 · 自动维护 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 pull --rebase origin main || echo "⚠️ rebase 冲突,尝试强制推送"
git push || echo "⚠️ 推送失败(可能有并发提交),下次巡检会重试"
fi

View File

@ -37,8 +37,8 @@ jobs:
- name: 📡 同步 commit 变更到 Notion
if: github.event_name == 'push'
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
CHANGES_DB_ID: ${{ secrets.CHANGES_DB_ID }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CHANGES_DB_ID: ${{ secrets.NOTION_CHANGE_LOG_DB_ID }}
EVENT_TYPE: commit
COMMIT_SHA: ${{ github.sha }}
COMMIT_MSG: ${{ github.event.head_commit.message }}
@ -56,8 +56,8 @@ jobs:
- name: 📡 同步 PR 变更到 Notion
if: github.event_name == 'pull_request'
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
CHANGES_DB_ID: ${{ secrets.CHANGES_DB_ID }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CHANGES_DB_ID: ${{ secrets.NOTION_CHANGE_LOG_DB_ID }}
EVENT_TYPE: pr
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}

View File

@ -34,7 +34,7 @@ jobs:
- name: 🔗 同步 syslog-inbox 到 Notion
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
SYSLOG_DB_ID: ${{ secrets.SYSLOG_DB_ID }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
SYSLOG_DB_ID: ${{ secrets.NOTION_SYSLOG_DB_ID }}
COMMIT_SHA: ${{ github.sha }}
run: node scripts/notion-bridge.js syslog

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: |
@ -245,17 +279,31 @@ jobs:
echo '⚠️ WebSocket pm2 启动失败'
fi
# 部署 src HLI 中间层
# 部署 src HLI 中间层(端口 3001对应 Nginx /api/v1/ 路由)
# 注意guanghulab-backend 已占用端口 3000HLI 使用 3001 避免冲突
if [ -f '${{ secrets.DEPLOY_PATH }}/src/index.js' ]; then
echo '📦 安装 src 依赖...'
cd '${{ secrets.DEPLOY_PATH }}'
npm install --production 2>/dev/null || echo '⚠️ src npm install 失败'
echo '🔄 重启 HLI 服务...'
pm2 restart guanghulab 2>/dev/null || \
pm2 start src/index.js --name guanghulab 2>/dev/null || \
echo '🔄 重启 HLI 服务(端口 3001...'
pm2 delete guanghulab 2>/dev/null || true
PORT=3001 pm2 start src/index.js --name guanghulab --update-env 2>/dev/null || \
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 +379,31 @@ jobs:
' proxy_set_header X-Real-IP \$remote_addr;' \
'}' \
'' \
'# Persona Studio API → persona-studio 后端端口 3002含 CORS 跨域支持)' \
'location /api/ps/ {' \
' add_header Access-Control-Allow-Origin * always;' \
' add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;' \
' add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;' \
' if (\$request_method = OPTIONS) {' \
' return 204;' \
' }' \
' 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/;' \
@ -342,6 +415,18 @@ jobs:
' proxy_cache_bypass \$http_upgrade;' \
'}' \
'' \
'# Persona Studio WebSocket → 预览进度推送端口 3002' \
'location /ws/preview {' \
' proxy_pass http://127.0.0.1:3002;' \
' proxy_http_version 1.1;' \
' proxy_set_header Upgrade \$http_upgrade;' \
' proxy_set_header Connection \"Upgrade\";' \
' 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_read_timeout 86400;' \
'}' \
'' \
'# WebSocket 反向代理 → 看板实时推送端口 8080' \
'location /ws {' \
' proxy_pass http://127.0.0.1:8080;' \
@ -418,8 +503,8 @@ jobs:
- name: 📡 推送部署记录到 Notion
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
CHANGES_DB_ID: ${{ secrets.CHANGES_DB_ID }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CHANGES_DB_ID: ${{ secrets.NOTION_CHANGE_LOG_DB_ID }}
EVENT_TYPE: commit
COMMIT_SHA: ${{ github.sha }}
COMMIT_MSG: "[CD 部署] ${{ github.event.head_commit.message }}"

View File

@ -1,4 +1,4 @@
name: 铸渊 · HoloLake Era 模块文档自动生成
name: 铸渊 · 光湖纪元 模块文档自动生成
on:
push:
@ -13,9 +13,14 @@ on:
- 'm11-module/**'
- 'm12-kanban/**'
- 'm15-cloud-drive/**'
- 'm18-health-check/**'
- 'dingtalk-bot/**'
- 'backend-integration/**'
- 'status-board/**'
- 'backend/**'
- 'frontend/**'
- 'notification/**'
- 'ticket-system/**'
- 'scripts/generate-module-doc.js'
workflow_dispatch:
@ -37,7 +42,7 @@ jobs:
with:
node-version: '20'
- name: 生成 HoloLake Era 模块文档
- name: 生成光湖纪元模块文档
run: node scripts/generate-module-doc.js
- name: 提交更新文档
@ -48,7 +53,7 @@ jobs:
if git diff --cached --quiet; then
echo "📋 文档无变化,跳过提交"
else
git commit -m "📋 自动更新 HoloLake Era 模块文档 · $(date -u +%Y-%m-%d\ %H:%M\ UTC)"
git commit -m "📋 自动更新光湖纪元模块文档 · $(date -u +%Y-%m-%d\ %H:%M\ UTC)"
git pull --rebase origin main || echo "⚠️ rebase 失败(可能有冲突),尝试直接推送..."
git push || {
echo "⚠️ 推送失败,尝试重新拉取后再推送..."
@ -91,7 +96,7 @@ jobs:
id: check
run: |
CHANGED_FILES="${{ steps.changed.outputs.files }}"
MODULES="m01-login m03-personality m05-user-center m06-ticket m07-dialogue-ui m10-cloud m11-module m12-kanban m15-cloud-drive dingtalk-bot backend-integration status-board"
MODULES="m01-login m03-personality m05-user-center m06-ticket m07-dialogue-ui m10-cloud m11-module m12-kanban m15-cloud-drive m18-health-check dingtalk-bot backend-integration status-board backend frontend notification ticket-system"
RESULT="pass"
DETAILS=""

View File

@ -0,0 +1,317 @@
name: Notion Callback Pipeline
# 📥 Notion → GitHub 回传闭环管道
#
# 当 Notion 侧核心大脑人格体处理完 SYSLOG 工单后,
# 通过 repository_dispatch 回传处理结果到 GitHub。
# 铸渊 Agent 接收回传,重新唤醒核心大脑认知进行二次核验,
# 确认新广播生成无误后,自动发送邮件通知开发者。
#
# 触发方式:
# Notion 侧 Agent 调用 GitHub API 发送 repository_dispatch 事件
# event_type: notion-syslog-callback
# client_payload: {
# broadcast_id: "BC-XXX-XXX-XX",
# dev_email: "developer@email.com",
# notion_result: "Notion 侧处理结果文本",
# ticket_id: "Notion 工单 ID",
# status: "approved" | "needs_revision"
# }
#
# 闭环流程:
# ① Notion 侧处理完成 → repository_dispatch 回传
# ② 铸渊 Agent 接收并解析回传数据
# ③ 重新唤醒铸渊核心大脑认知(二次核验)
# ④ 核心大脑核对广播生成无误
# ⑤ 铸渊 Agent 发送邮件通知开发者
# ⑥ 闭环结束
#
# 依赖 Secrets
# LLM_API_KEY 第三方 LLM 平台密钥
# LLM_BASE_URL 第三方 LLM 平台 API 地址
# NOTION_API_TOKEN Notion API token
# CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 ID
# PORTRAIT_DB_ID 开发者动态画像库数据库 ID
# FINGERPRINT_DB_ID 模块指纹注册表数据库 ID
# SMTP_USER QQ 邮箱地址
# SMTP_PASS QQ 邮箱 SMTP 授权码
on:
repository_dispatch:
types: [notion-syslog-callback]
jobs:
verify-and-notify:
name: 📥 Notion 回传 · 二次核验 · 邮件通知
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: 📥 Parse Notion callback
id: callback
uses: actions/github-script@v7
with:
script: |
const payload = context.payload.client_payload || {};
const broadcastId = payload.broadcast_id || '';
const devEmail = payload.dev_email || '';
const notionResult = payload.notion_result || '';
const ticketId = payload.ticket_id || '';
const status = payload.status || 'unknown';
if (!broadcastId) {
core.setFailed('❌ 缺少 broadcast_id');
return;
}
core.setOutput('broadcast_id', broadcastId);
core.setOutput('dev_email', devEmail);
core.setOutput('notion_result', notionResult);
core.setOutput('ticket_id', ticketId);
core.setOutput('status', status);
console.log(`📥 Notion 回传: 广播=${broadcastId}, 状态=${status}, 工单=${ticketId}`);
- name: 🔍 铸渊 Agent · 模块二次验证
id: verify
env:
SYSLOG_CONTENT: ${{ steps.callback.outputs.notion_result }}
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
AUTHOR: notion-callback
run: node scripts/verify-modules.js
- name: 🧠 二次核验 · 唤醒铸渊核心大脑
id: persona
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
SUBMIT_TYPE: syslog
SUBMIT_CONTENT: ${{ steps.callback.outputs.notion_result }}
AUTHOR: notion-callback
MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
NOTION_CALLBACK_RESULT: ${{ steps.callback.outputs.notion_result }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
run: node scripts/wake-persona.js
- name: 📡 推送广播到 GitHubPhase B4
if: steps.persona.outcome == 'success' && steps.callback.outputs.status == 'approved'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
DEVELOPER_NAME: notion-callback
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
run: node scripts/push-broadcast-to-github.js
- name: 📝 回写 Notion 工单状态
if: steps.callback.outputs.ticket_id
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
TICKET_ID: ${{ steps.callback.outputs.ticket_id }}
CALLBACK_STATUS: ${{ steps.callback.outputs.status }}
run: |
node -e "
var https = require('https');
var token = process.env.NOTION_TOKEN || '';
var ticketId = process.env.TICKET_ID || '';
var status = process.env.CALLBACK_STATUS || 'unknown';
if (!token || !ticketId) {
console.log('⚠️ 跳过工单状态更新');
process.exit(0);
}
var notionStatus = status === 'approved' ? '✅ 已完成' : '⚠️ 异常·等人工介入';
var body = JSON.stringify({
properties: {
'状态': { select: { name: notionStatus } }
}
});
var opts = {
hostname: 'api.notion.com',
port: 443,
path: '/v1/pages/' + ticketId,
method: 'PATCH',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28',
'Content-Length': Buffer.byteLength(body)
}
};
var req = https.request(opts, function(res) {
var data = '';
res.on('data', function(c) { data += c; });
res.on('end', function() {
if (res.statusCode >= 200 && res.statusCode < 300) {
console.log('✅ 工单状态已更新为 ' + notionStatus);
} else {
console.log('⚠️ 工单更新失败: ' + res.statusCode);
}
});
});
req.on('error', function(e) { console.log('⚠️ ' + e.message); });
req.write(body);
req.end();
"
- name: 📧 发送最终确认邮件
env:
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
EMAIL_TO: ${{ steps.callback.outputs.dev_email }}
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
NOTION_STATUS: ${{ steps.callback.outputs.status }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
run: |
node -e "
const nodemailer = require('nodemailer');
const user = process.env.SMTP_USER || '';
const pass = process.env.SMTP_PASS || '';
const to = process.env.EMAIL_TO || '';
const broadcastId = process.env.BROADCAST_ID || '';
const notionStatus = process.env.NOTION_STATUS || 'unknown';
const result = process.env.PERSONA_RESULT || '(处理中)';
if (!user || !pass) {
console.log('⚠️ SMTP not configured, skipping email');
process.exit(0);
}
if (!to) {
console.log('⚠️ No recipient email, skipping');
process.exit(0);
}
const isApproved = notionStatus === 'approved';
const subjectText = isApproved
? '[光湖系统] ' + broadcastId + ' · ✅ 新广播已生成(已核验)'
: '[光湖系统] ' + broadcastId + ' · ⚠️ 广播需修订';
const statusBadge = isApproved
? '<span style=\"background:#22c55e;color:#fff;padding:4px 12px;border-radius:4px;font-weight:bold\">✅ 已核验通过</span>'
: '<span style=\"background:#f59e0b;color:#fff;padding:4px 12px;border-radius:4px;font-weight:bold\">⚠️ 需修订</span>';
const resultHtml = result
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
const html = [
'<div style=\"font-family:sans-serif;max-width:700px;margin:0 auto;padding:24px;background:#0f172a;color:#e2e8f0;border-radius:12px\">',
'<div style=\"text-align:center;padding:16px 0;border-bottom:1px solid #334155\">',
'<h2 style=\"color:#60a5fa;margin:0\">🌊 光湖系统 · Notion 回传核验通知</h2>',
'<p style=\"color:#94a3b8;font-size:14px;margin:8px 0 0\">HoloLake · 铸渊核心大脑二次核验完成</p>',
'</div>',
'<div style=\"padding:20px 0\">',
'<p style=\"color:#22d3ee;font-weight:bold\">📡 ' + broadcastId + '</p>',
'<p style=\"margin:12px 0\">' + statusBadge + '</p>',
'<div style=\"background:#1e293b;padding:16px;border-radius:8px;margin:12px 0;line-height:1.8\">' + resultHtml + '</div>',
'</div>',
'<div style=\"border-top:1px solid #334155;padding:16px 0;text-align:center\">',
'<a href=\"https://github.com/qinfendebingshuo/guanghulab\" style=\"display:inline-block;padding:8px 20px;background:linear-gradient(135deg,#3b82f6,#22d3ee);color:#fff;text-decoration:none;border-radius:6px;font-weight:600\">↩ 回到仓库继续开发</a>',
'<p style=\"color:#64748b;font-size:12px;margin:12px 0 0\">🌀 铸渊 · 代码守护人格体 · Notion↔GitHub 闭环确认</p>',
'</div>',
'</div>'
].join('\n');
const transporter = nodemailer.createTransport({
host: 'smtp.qq.com',
port: 465,
secure: true,
auth: { user, pass }
});
transporter.sendMail({
from: '\"光湖系统\" <' + user + '>',
to: to,
subject: subjectText,
html: html
}).then((info) => {
console.log('✅ 最终确认邮件已发送: ' + info.messageId);
}).catch((err) => {
console.log('⚠️ 邮件发送失败: ' + err.message);
});
"
- name: 📝 更新大脑记忆
run: |
node -e "
const fs = require('fs');
const path = require('path');
const memoryPath = path.join('.github', 'brain', 'memory.json');
let memory = {};
try {
memory = JSON.parse(fs.readFileSync(memoryPath, 'utf8'));
} catch (_) {
memory = { events: [] };
}
if (!memory.events) memory.events = [];
const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
const status = process.env.NOTION_STATUS || 'unknown';
const now = new Date().toISOString();
// 检查是否已存在相同事件(按 broadcast_id 精确匹配 + 同类型 + 同日期)
const eventKey = 'Notion 回传核验 · ' + broadcastId;
const todayStr = now.slice(0, 10);
const existingIdx = memory.events.findIndex(function(e) {
return e.type === 'notion_callback' &&
e.broadcast_id === broadcastId &&
e.date === todayStr;
});
const newEvent = {
date: todayStr,
type: 'notion_callback',
broadcast_id: broadcastId,
description: eventKey + ' · 状态: ' + status,
by: '铸渊Agent·Notion回传管道'
};
if (existingIdx >= 0) {
// 同一 broadcast_id 只保留最新的一条
memory.events[existingIdx] = newEvent;
console.log(' 已更新同日同广播事件');
} else {
memory.events.unshift(newEvent);
}
// 保留最近 20 条事件
if (memory.events.length > 20) {
memory.events = memory.events.slice(0, 20);
}
fs.writeFileSync(memoryPath, JSON.stringify(memory, null, 2) + '\n');
console.log('✅ 大脑记忆已更新');
"
env:
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
NOTION_STATUS: ${{ steps.callback.outputs.status }}
- name: 📤 Commit memory update
run: |
git config user.name "zhuyuan-agent[bot]"
git config user.email "zhuyuan-agent[bot]@users.noreply.github.com"
git add .github/brain/memory.json
git diff --cached --quiet || git commit -m "🧠 Notion回传核验 · ${{ steps.callback.outputs.broadcast_id }}"
git push || echo "⚠️ Push skipped (no changes or conflict)"

View File

@ -37,10 +37,10 @@ jobs:
- name: 🧪 执行连通性测试
id: test
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
SIGNAL_LOG_DB_ID: ${{ vars.SIGNAL_LOG_DB_ID }}
CHANGES_DB_ID: ${{ secrets.CHANGES_DB_ID }}
SYSLOG_DB_ID: ${{ secrets.SYSLOG_DB_ID }}
CHANGES_DB_ID: ${{ secrets.NOTION_CHANGE_LOG_DB_ID }}
SYSLOG_DB_ID: ${{ secrets.NOTION_SYSLOG_DB_ID }}
run: node scripts/notion-connectivity-test.js
- name: 提交信号日志

47
.github/workflows/notion-heartbeat.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Notion Heartbeat Monitor
# 💓 Phase B3 · 工单心跳监控
#
# 每 5 分钟检测 Notion 工单队列中的待处理工单:
# - 超过 5 分钟未回执 → 重新触发人格体唤醒(最多 3 次)
# - 3 次重试仍无回执 → 标记工单异常 + 发邮件通知冰朔
#
# 依赖 Secrets
# NOTION_API_TOKEN Notion API token
# NOTION_TICKET_DB_ID 工单队列数据库 ID
# SMTP_USER 邮件发送者
# SMTP_PASS 邮件授权码
on:
schedule:
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
heartbeat:
name: 💓 工单心跳检测
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: 💓 执行心跳检测
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
ALERT_EMAIL: '565183519@qq.com'
run: node scripts/notion-heartbeat.js

248
.github/workflows/persona-invoke.yml vendored Normal file
View File

@ -0,0 +1,248 @@
name: Persona Invoke Endpoint
# 🔗 Phase B2 · Notion Agent → 铸渊人格体唤醒
#
# Notion Agent 通过 workflow_dispatch 触发此工作流,
# 等效于 POST /api/persona/invoke 接口。
#
# 完整流程:
# ① Notion Agent 发送 workflow_dispatch传入工单信息
# ② 铸渊 Agent 从 Notion 读取工单内容
# ③ 唤醒人格体Claude API处理 SYSLOG
# ④ 处理结果写回 Notion 工单receipt_status = completed
# ⑤ 广播文件推送到 GitHub 仓库 broadcasts/{dev_id}/
#
# 依赖 Secrets
# LLM_API_KEY 第三方 LLM 平台密钥
# LLM_BASE_URL 第三方 LLM 平台 API 地址
# NOTION_API_TOKEN Notion API token
# NOTION_TICKET_DB_ID 工单队列数据库 ID
# CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 ID
# PORTRAIT_DB_ID 开发者动态画像库 ID
# FINGERPRINT_DB_ID 模块指纹注册表 ID
# INVOKE_API_KEY 调用鉴权密钥
on:
workflow_dispatch:
inputs:
work_order_id:
description: 'Notion 工单页面 ID'
required: true
type: string
task_id:
description: '广播编号(如 BC-M23-001-AW'
required: true
type: string
developer:
description: '开发者信息(如 DEV-012 Awen'
required: false
default: 'unknown'
type: string
syslog_raw:
description: 'SYSLOG JSON 原文(可选,如为空则从 Notion 读取)'
required: false
default: ''
type: string
action:
description: '动作类型'
required: false
default: 'process_syslog'
type: string
jobs:
invoke:
name: 🔗 唤醒人格体处理 SYSLOG
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: 🔍 模块验证
id: verify
env:
SYSLOG_CONTENT: ${{ inputs.syslog_raw }}
BROADCAST_ID: ${{ inputs.task_id }}
AUTHOR: ${{ inputs.developer }}
run: node scripts/verify-modules.js
- name: 🧠 唤醒人格体
id: persona
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
BROADCAST_ID: ${{ inputs.task_id }}
SUBMIT_TYPE: syslog
SUBMIT_CONTENT: ${{ inputs.syslog_raw }}
AUTHOR: ${{ inputs.developer }}
MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
run: node scripts/wake-persona.js
- name: 📝 回写 Notion 工单
if: always()
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
WORK_ORDER_ID: ${{ inputs.work_order_id }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
TASK_ID: ${{ inputs.task_id }}
run: |
node -e "
var https = require('https');
var token = process.env.NOTION_TOKEN || '';
var workOrderId = process.env.WORK_ORDER_ID || '';
var result = process.env.PERSONA_RESULT || '(no result)';
var taskId = process.env.TASK_ID || '';
if (!token || !workOrderId) {
console.log('⚠️ 缺少 Notion 配置,跳过回写');
process.exit(0);
}
// 更新工单状态为 ✅ 已完成
var body = JSON.stringify({
properties: {
'状态': { select: { name: '✅ 已完成' } }
}
});
var opts = {
hostname: 'api.notion.com',
port: 443,
path: '/v1/pages/' + workOrderId,
method: 'PATCH',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28',
'Content-Length': Buffer.byteLength(body)
}
};
var req = https.request(opts, function(res) {
var data = '';
res.on('data', function(c) { data += c; });
res.on('end', function() {
if (res.statusCode >= 200 && res.statusCode < 300) {
console.log('✅ 工单状态已更新为 ✅ 已完成');
} else {
console.log('⚠️ 工单状态更新失败: ' + res.statusCode);
}
});
});
req.on('error', function(e) { console.log('⚠️ ' + e.message); });
req.write(body);
req.end();
// 追加处理结果到工单
var appendBody = JSON.stringify({
children: [{
object: 'block',
type: 'heading_2',
heading_2: {
rich_text: [{ type: 'text', text: { content: '🧠 人格体处理结果 · receipt_status: completed' } }]
}
}, {
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
}
}]
});
var appendOpts = {
hostname: 'api.notion.com',
port: 443,
path: '/v1/blocks/' + workOrderId + '/children',
method: 'PATCH',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28',
'Content-Length': Buffer.byteLength(appendBody)
}
};
var req2 = https.request(appendOpts, function(res) {
var data = '';
res.on('data', function(c) { data += c; });
res.on('end', function() {
if (res.statusCode >= 200 && res.statusCode < 300) {
console.log('✅ 处理结果已追加到工单');
} else {
console.log('⚠️ 结果追加失败: ' + res.statusCode);
}
});
});
req2.on('error', function(e) { console.log('⚠️ ' + e.message); });
req2.write(appendBody);
req2.end();
"
- name: 📡 推送广播到 GitHub
if: steps.persona.outcome == 'success'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BROADCAST_ID: ${{ inputs.task_id }}
DEVELOPER_ID: ''
DEVELOPER_NAME: ${{ inputs.developer }}
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
run: node scripts/push-broadcast-to-github.js
- name: 🔴 失败处理
if: failure()
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
WORK_ORDER_ID: ${{ inputs.work_order_id }}
run: |
node -e "
var https = require('https');
var token = process.env.NOTION_TOKEN || '';
var workOrderId = process.env.WORK_ORDER_ID || '';
if (!token || !workOrderId) process.exit(0);
var body = JSON.stringify({
properties: {
'状态': { select: { name: '⚠️ 异常·等人工介入' } }
}
});
var opts = {
hostname: 'api.notion.com',
port: 443,
path: '/v1/pages/' + workOrderId,
method: 'PATCH',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28',
'Content-Length': Buffer.byteLength(body)
}
};
var req = https.request(opts, function(res) {
var data = '';
res.on('data', function(c) { data += c; });
res.on('end', function() {
console.log('工单状态已标记为 ⚠️ 异常');
});
});
req.on('error', function(e) { console.log(e.message); });
req.write(body);
req.end();
"

View File

@ -0,0 +1,316 @@
name: "🔧 铸渊 · PM2 服务诊断与健康检查"
# ━━━ PM2 服务诊断 + 全系统健康检查 + Notion 报告 ━━━
# 手动触发:对服务器 PM2 进程进行全面排查,
# 自动处理 errored 进程,并将诊断报告写入 Notion 工单队列。
#
# 必需 GitHub Secrets:
# DEPLOY_HOST — 服务器 IP
# DEPLOY_USER — SSH 用户名
# DEPLOY_KEY — SSH 私钥(完整 PEM 内容)
# DEPLOY_PATH — 网站根目录(如 /var/www/guanghulab
#
# 可选 Secrets有则自动写 Notion 工单):
# NOTION_API_TOKEN — Notion 集成 token
# NOTION_TICKET_DB_ID — 工单队列数据库 ID默认 84edd0640bf146a9a5a7840107013e8c
on:
workflow_dispatch:
inputs:
auto_cleanup:
description: '是否自动清理 errored 进程(删除启动路径不存在的进程)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
permissions:
contents: read
jobs:
diagnose:
name: "🔍 PM2 诊断 + 系统健康检查"
runs-on: ubuntu-latest
steps:
- name: 📥 检出代码
uses: actions/checkout@v4
- name: 🟢 配置 Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: 🔑 配置 SSH 连接
run: |
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 || {
echo "⚠️ ssh-keyscan 失败,使用 StrictHostKeyChecking=no 后备"
}
# ━━━ 阶段一PM2 服务诊断 ━━━
- name: "🔧 阶段一 · PM2 errored 服务排查"
id: pm2_diagnose
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 阶段一 · PM2 errored 服务排查"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
PM2_REPORT=$(ssh -i ~/.ssh/deploy_key \
-o StrictHostKeyChecking=no \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" '
echo "══════════════════════════════════════"
echo " PM2 进程状态概览"
echo "══════════════════════════════════════"
pm2 status 2>/dev/null || echo "❌ pm2 未安装或不可用"
echo ""
echo "══════════════════════════════════════"
echo " errored 进程详细日志"
echo "══════════════════════════════════════"
# 遍历所有 errored 进程
ERRORED_LIST=$(pm2 jlist 2>/dev/null | python3 -c "
import sys, json
try:
procs = json.load(sys.stdin)
for p in procs:
if p.get(\"pm2_env\", {}).get(\"status\") == \"errored\":
name = p.get(\"name\", \"unknown\")
pm_id = p.get(\"pm_id\", \"?\")
script = p.get(\"pm2_env\", {}).get(\"pm_exec_path\", \"unknown\")
restarts = p.get(\"pm2_env\", {}).get(\"restart_time\", 0)
print(f\"{pm_id}|{name}|{script}|{restarts}\")
except:
pass
" 2>/dev/null)
if [ -z "$ERRORED_LIST" ]; then
echo "✅ 没有 errored 进程"
else
echo "$ERRORED_LIST" | while IFS="|" read -r pm_id name script restarts; do
echo ""
echo "━━━ 进程: $name (PM2 ID: $pm_id) ━━━"
echo " 启动脚本: $script"
echo " 重启次数: $restarts"
# 检查脚本文件是否存在
if [ -f "$script" ]; then
echo " 脚本文件: ✅ 存在"
else
echo " 脚本文件: ❌ 不存在"
fi
# 查看最近日志
echo " 最近错误日志 (最后 20 行):"
pm2 logs "$name" --lines 20 --nostream --err 2>/dev/null || echo " (无法读取日志)"
echo ""
done
fi
echo ""
echo "══════════════════════════════════════"
echo " 所有进程详细信息"
echo "══════════════════════════════════════"
pm2 jlist 2>/dev/null | python3 -c "
import sys, json
try:
procs = json.load(sys.stdin)
for p in procs:
env = p.get(\"pm2_env\", {})
print(f\" [{p.get(\"pm_id\")}] {p.get(\"name\")} | status={env.get(\"status\")} | script={env.get(\"pm_exec_path\",\"?\")} | restarts={env.get(\"restart_time\",0)} | memory={p.get(\"monit\",{}).get(\"memory\",0)}\")
except Exception as e:
print(f\" 解析失败: {e}\")
" 2>/dev/null
echo ""
echo "══════════════════════════════════════"
echo " 端口占用情况"
echo "══════════════════════════════════════"
netstat -tlnp 2>/dev/null | grep -E "3000|3001|3002|3721|8080" || \
ss -tlnp 2>/dev/null | grep -E "3000|3001|3002|3721|8080" || \
echo " (netstat/ss 不可用)"
')
echo "$PM2_REPORT"
# 保存到文件供后续步骤使用
echo "$PM2_REPORT" > /tmp/pm2-report.txt
# 设置输出
{
echo "pm2_report<<REPORT_EOF"
echo "$PM2_REPORT"
echo "REPORT_EOF"
} >> "$GITHUB_OUTPUT"
# ━━━ 阶段一 · 自动清理 ━━━
- name: "🧹 阶段一 · 自动清理 errored 进程"
if: ${{ inputs.auto_cleanup == 'true' }}
id: pm2_cleanup
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 自动清理 errored 进程"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
CLEANUP_REPORT=$(ssh -i ~/.ssh/deploy_key \
-o StrictHostKeyChecking=no \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" '
ERRORED_LIST=$(pm2 jlist 2>/dev/null | python3 -c "
import sys, json
try:
procs = json.load(sys.stdin)
for p in procs:
if p.get(\"pm2_env\", {}).get(\"status\") == \"errored\":
name = p.get(\"name\", \"unknown\")
script = p.get(\"pm2_env\", {}).get(\"pm_exec_path\", \"unknown\")
print(f\"{name}|{script}\")
except:
pass
" 2>/dev/null)
CLEANED=""
SKIPPED=""
if [ -z "$ERRORED_LIST" ]; then
echo "✅ 没有需要清理的 errored 进程"
else
echo "$ERRORED_LIST" | while IFS="|" read -r name script; do
if [ ! -f "$script" ]; then
echo "🗑️ 删除 $name脚本 $script 不存在)"
pm2 delete "$name" 2>/dev/null && echo " ✅ 已删除" || echo " ❌ 删除失败"
else
echo "⚠️ 保留 $name脚本 $script 存在,可能是运行时错误)"
echo " 尝试重启..."
pm2 restart "$name" 2>/dev/null && echo " ✅ 重启成功" || echo " ❌ 重启失败"
fi
done
echo ""
echo "📊 清理后 PM2 状态:"
pm2 status 2>/dev/null
pm2 save 2>/dev/null || echo "⚠️ pm2 save 失败"
fi
')
echo "$CLEANUP_REPORT"
echo "$CLEANUP_REPORT" > /tmp/cleanup-report.txt
{
echo "cleanup_report<<CLEANUP_EOF"
echo "$CLEANUP_REPORT"
echo "CLEANUP_EOF"
} >> "$GITHUB_OUTPUT"
# ━━━ 阶段二:全系统健康检查 ━━━
- name: "🏥 阶段二 · 全系统健康检查"
id: health_check
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 阶段二 · 全系统健康检查"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
HEALTH_REPORT=$(ssh -i ~/.ssh/deploy_key \
-o StrictHostKeyChecking=no \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" '
echo "══════════════════════════════════════"
echo " Nginx 状态"
echo "══════════════════════════════════════"
nginx -t 2>&1 || echo "❌ Nginx 配置检测失败"
systemctl status nginx --no-pager -l 2>/dev/null | head -15 || echo "⚠️ systemctl 不可用"
echo ""
echo "══════════════════════════════════════"
echo " SSL 证书有效期"
echo "══════════════════════════════════════"
if [ -f "/etc/letsencrypt/live/guanghulab.com/fullchain.pem" ]; then
openssl x509 -in /etc/letsencrypt/live/guanghulab.com/fullchain.pem -noout -dates 2>/dev/null || echo "⚠️ 无法读取 SSL 证书"
else
echo "⚠️ SSL 证书文件不存在于默认路径"
# 尝试查找其他证书路径
find /etc/letsencrypt/live/ -name "fullchain.pem" 2>/dev/null | head -3 || echo " 未找到 Let'\''s Encrypt 证书"
find /etc/nginx/ -name "*.pem" -o -name "*.crt" 2>/dev/null | head -3 || echo " 未找到 Nginx SSL 文件"
fi
echo ""
echo "══════════════════════════════════════"
echo " 磁盘空间"
echo "══════════════════════════════════════"
df -h 2>/dev/null || echo "❌ df 命令不可用"
echo ""
echo "══════════════════════════════════════"
echo " 内存使用"
echo "══════════════════════════════════════"
free -h 2>/dev/null || echo "❌ free 命令不可用"
echo ""
echo "══════════════════════════════════════"
echo " PM2 最终状态"
echo "══════════════════════════════════════"
pm2 status 2>/dev/null || echo "❌ pm2 不可用"
echo ""
echo "══════════════════════════════════════"
echo " Node.js 版本"
echo "══════════════════════════════════════"
node -v 2>/dev/null || echo "❌ Node.js 未安装"
npm -v 2>/dev/null || echo "❌ npm 未安装"
echo ""
echo "══════════════════════════════════════"
echo " 网站可访问性检查"
echo "══════════════════════════════════════"
curl -sI https://guanghulab.com 2>/dev/null | head -5 || echo "⚠️ HTTPS 访问失败"
echo "---"
curl -s http://127.0.0.1:3000/health 2>/dev/null | head -c 200 || echo "⚠️ 端口 3000 无响应"
echo ""
echo "---"
curl -s http://127.0.0.1:3001/api/v1/ 2>/dev/null | head -c 200 || echo "⚠️ 端口 3001 无响应"
echo ""
echo "---"
curl -s http://127.0.0.1:3002/api/ps/ 2>/dev/null | head -c 200 || echo "⚠️ 端口 3002 无响应"
echo ""
echo ""
echo "══════════════════════════════════════"
echo " 系统运行时间"
echo "══════════════════════════════════════"
uptime 2>/dev/null || echo "❌ uptime 不可用"
')
echo "$HEALTH_REPORT"
echo "$HEALTH_REPORT" > /tmp/health-report.txt
{
echo "health_report<<HEALTH_EOF"
echo "$HEALTH_REPORT"
echo "HEALTH_EOF"
} >> "$GITHUB_OUTPUT"
# ━━━ 阶段三:生成 Notion 诊断报告 ━━━
- name: "📋 阶段三 · 写入 Notion 工单"
if: ${{ secrets.NOTION_API_TOKEN != '' }}
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID || '84edd0640bf146a9a5a7840107013e8c' }}
PM2_REPORT: ${{ steps.pm2_diagnose.outputs.pm2_report }}
CLEANUP_REPORT: ${{ steps.pm2_cleanup.outputs.cleanup_report }}
HEALTH_REPORT: ${{ steps.health_check.outputs.health_report }}
run: node scripts/server-diagnose-report.js
- name: "📋 阶段三 · 写入 Notion 工单(环境检查)"
if: ${{ secrets.NOTION_API_TOKEN == '' }}
run: |
echo "⚠️ Notion credentials 未配置,跳过工单创建"
echo "诊断报告仅输出到 Actions 日志"
- name: "✅ 诊断完成"
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ✅ PM2 诊断完毕 · $(date '+%Y-%m-%d %H:%M:%S')"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

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

63
.github/workflows/push-broadcast.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: 铸渊 · Push Broadcast · Notion → 飞书文档B
# 链路2新广播推送
# Notion 新广播页面 → 追加到飞书文档B顶部
#
# 依赖 Secrets
# NOTION_TOKEN Notion API token
# FEISHU_APP_ID 飞书应用 App ID
# FEISHU_APP_SECRET 飞书应用 App Secret
# FEISHU_DOC_B_ID 飞书文档B的 document_id
#
# dispatch payload
# broadcast_page_id Notion 广播页面 ID
on:
repository_dispatch:
types: [push-broadcast]
workflow_dispatch:
inputs:
broadcast_page_id:
description: 'Notion 广播页面 ID'
required: true
jobs:
push-broadcast:
name: 📡 推送广播到飞书
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 📡 推送广播 → 飞书文档B
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
BROADCAST_PAGE_ID: ${{ github.event.client_payload.broadcast_page_id || github.event.inputs.broadcast_page_id }}
FEISHU_APP_ID: ${{ secrets.FEISHU_APP_ID }}
FEISHU_APP_SECRET: ${{ secrets.FEISHU_APP_SECRET }}
FEISHU_DOC_B_ID: ${{ secrets.FEISHU_DOC_B_ID }}
run: node scripts/push-broadcast.js
- name: 📢 通知飞书群有新广播
if: success()
env:
FEISHU_APP_ID: ${{ secrets.FEISHU_APP_ID }}
FEISHU_APP_SECRET: ${{ secrets.FEISHU_APP_SECRET }}
ALERT_CHAT_ID: ${{ secrets.FEISHU_ALERT_CHAT_ID }}
run: node scripts/send-feishu-alert.js "push-broadcast" "📡 新广播已推送到飞书文档B请查看飞书广播收件箱。" || true
- name: 🔴 失败告警 → 飞书通知
if: failure()
env:
FEISHU_APP_ID: ${{ secrets.FEISHU_APP_ID }}
FEISHU_APP_SECRET: ${{ secrets.FEISHU_APP_SECRET }}
ALERT_CHAT_ID: ${{ secrets.FEISHU_ALERT_CHAT_ID }}
run: node scripts/send-feishu-alert.js "push-broadcast"

61
.github/workflows/receive-syslog.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: 铸渊 · Receive SYSLOG · 飞书机器人 → GitHub → Notion
# 链路3SYSLOG 回传
# 飞书机器人发来的 SYSLOG → 存入仓库 + 创建 Notion 条目 + 创建霜砚工单
#
# 依赖 Secrets
# NOTION_TOKEN Notion API token
# NOTION_SYSLOG_DB_ID SYSLOG 收件箱数据库 ID
# NOTION_TICKET_DB_ID 霜砚工单数据库 ID
#
# dispatch payload
# syslog SYSLOG JSON 全文
on:
repository_dispatch:
types: [receive-syslog]
jobs:
receive-syslog:
name: 📥 接收 SYSLOG 回传
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 📥 处理 SYSLOG
env:
SYSLOG_JSON: ${{ toJSON(github.event.client_payload.syslog) }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
NOTION_SYSLOG_DB_ID: ${{ secrets.NOTION_SYSLOG_DB_ID }}
NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
run: node scripts/receive-syslog.js
- name: 💾 提交 SYSLOG 文件到仓库
run: |
git config user.name "zhuyuan-bot"
git config user.email "zhuyuan-bot@guanghulab.com"
git add syslog/
if git diff --cached --quiet; then
echo "No new syslog files to commit"
else
git pull --rebase origin main || true
git commit -m "📥 铸渊接收 SYSLOG 回传 [skip ci]"
git push origin main
fi
- name: 🔴 失败告警 → 飞书通知
if: failure()
env:
FEISHU_APP_ID: ${{ secrets.FEISHU_APP_ID }}
FEISHU_APP_SECRET: ${{ secrets.FEISHU_APP_SECRET }}
ALERT_CHAT_ID: ${{ secrets.FEISHU_ALERT_CHAT_ID }}
run: node scripts/send-feishu-alert.js "receive-syslog"

49
.github/workflows/sync-login-entry.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: 铸渊 · Sync Login Entry · Notion → 飞书文档A
# 链路1登录入口同步
# Notion 霜砚登录入口页面 → 全量替换飞书文档A
#
# 依赖 Secrets
# NOTION_TOKEN Notion API token
# NOTION_LOGIN_PAGE_ID Notion 登录入口页面 ID
# FEISHU_APP_ID 飞书应用 App ID
# FEISHU_APP_SECRET 飞书应用 App Secret
# FEISHU_DOC_A_ID 飞书文档A的 document_id
on:
repository_dispatch:
types: [sync-login-entry]
workflow_dispatch:
jobs:
sync-login-entry:
name: 📋 同步登录入口到飞书
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 🔗 同步 Notion 登录入口 → 飞书文档A
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
NOTION_LOGIN_PAGE_ID: ${{ secrets.NOTION_LOGIN_PAGE_ID }}
FEISHU_APP_ID: ${{ secrets.FEISHU_APP_ID }}
FEISHU_APP_SECRET: ${{ secrets.FEISHU_APP_SECRET }}
FEISHU_DOC_A_ID: ${{ secrets.FEISHU_DOC_A_ID }}
run: node scripts/sync-login-entry.js
- name: 🔴 失败告警 → 飞书通知
if: failure()
env:
FEISHU_APP_ID: ${{ secrets.FEISHU_APP_ID }}
FEISHU_APP_SECRET: ${{ secrets.FEISHU_APP_SECRET }}
ALERT_CHAT_ID: ${{ secrets.FEISHU_ALERT_CHAT_ID }}
run: node scripts/send-feishu-alert.js "sync-login-entry"

View File

@ -0,0 +1,58 @@
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
concurrency:
group: sync-persona-studio
cancel-in-progress: true
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: env.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

@ -0,0 +1,343 @@
name: SYSLOG Auto Pipeline
# 📡 SYSLOG 自助提交系统 · 全自动闭环
#
# 开发者在 GitHub Discussion 提交 SYSLOG 或提问
# → Actions 自动解析 → 模块上传验证 → 调用 LLM API 唤醒人格体
# → 模块闭环测试 → 写入 Notion 工单 → 发邮件给开发者 → Discussion 回复
#
# 闭环流程:
# ① 开发者提交 SYSLOG → Discussion 触发
# ② 铸渊 Agent 解析提交内容
# ③ 铸渊 Agent 检测模块是否上传到仓库
# ④ 唤醒铸渊核心大脑认知(注入模块验证结果)
# ⑤ 核心大脑处理 SYSLOG + 生成广播
# ⑥ 铸渊 Agent 推送 Notion 工单(触发 Notion 侧处理)
# ⑦ 邮件通知开发者
# ⑧ Discussion 回复闭环
#
# 依赖 Secrets
# LLM_API_KEY 第三方 LLM 平台密钥(必须)
# LLM_BASE_URL 第三方 LLM 平台 API 地址(必须,如 https://api.xxx.com/v1
# NOTION_API_TOKEN Notion API token
# NOTION_TICKET_DB_ID 霜砚工单数据库 ID
# CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 IDv4.0 协议动态注入)
# PORTRAIT_DB_ID 开发者动态画像库数据库 IDv4.0 协议动态注入)
# FINGERPRINT_DB_ID 模块指纹注册表数据库 IDv4.0 协议动态注入)
# SMTP_USER QQ 邮箱地址
# SMTP_PASS QQ 邮箱 SMTP 授权码
on:
discussion:
types: [created]
jobs:
process:
name: 📡 处理 SYSLOG 提交 / 广播提问
runs-on: ubuntu-latest
if: contains(github.event.discussion.category.name, 'SYSLOG')
permissions:
contents: write
discussions: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: 🔍 Parse submission
id: parse
uses: actions/github-script@v7
with:
script: |
const body = context.payload.discussion.body || '';
const title = context.payload.discussion.title || '';
// Parse structured fields from discussion body
function extractField(text, label) {
// Match form field format: ### Label\n\nValue
const regex = new RegExp('###\\s*' + label + '\\s*\\n+([^\\n#]+)', 'i');
const match = text.match(regex);
return match ? match[1].trim() : '';
}
const broadcastId = extractField(body, '广播编号') || '';
const submitType = extractField(body, '类型') || '';
const email = extractField(body, '你的邮箱') || '';
// Extract content (everything after "### 内容")
const contentMatch = body.match(/###\s*内容\s*\n+([\s\S]*?)$/i);
const content = contentMatch ? contentMatch[1].trim() : '';
// Determine type from title or body
let type = 'question'; // default
if (submitType.includes('SYSLOG') || title.includes('SYSLOG')) {
type = 'syslog';
} else if (submitType.includes('提问') || title.includes('提问')) {
type = 'question';
}
// Validate
if (!broadcastId) {
core.setFailed('❌ 缺少广播编号,请在提交时填写广播编号');
return;
}
if (!email) {
core.setFailed('❌ 缺少邮箱,请在提交时填写你的邮箱');
return;
}
if (!content) {
core.setFailed('❌ 缺少内容,请粘贴你的 SYSLOG 或问题');
return;
}
core.setOutput('broadcast_id', broadcastId);
core.setOutput('type', type);
core.setOutput('email', email);
core.setOutput('content', content);
core.setOutput('discussion_number', context.payload.discussion.number);
core.setOutput('author', context.payload.discussion.user.login);
console.log(`📡 解析完成: 广播=${broadcastId}, 类型=${type}, 邮箱=${email}`);
- name: 🔍 铸渊 Agent · 模块上传验证
id: verify
env:
SYSLOG_CONTENT: ${{ steps.parse.outputs.content }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
AUTHOR: ${{ steps.parse.outputs.author }}
run: node scripts/verify-modules.js
- name: 🧠 Auto-detect and wake up persona
id: persona
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
AUTHOR: ${{ steps.parse.outputs.author }}
MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
run: node scripts/wake-persona.js
- name: 📋 创建标准化 Notion 工单Phase B1
id: ticket
if: ${{ secrets.NOTION_API_TOKEN }}
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
DEVELOPER: ${{ steps.parse.outputs.author }}
SYSLOG_RAW: ${{ steps.parse.outputs.content }}
run: node scripts/create-standardized-ticket.js
- name: 📡 推送广播到 GitHubPhase B4
if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
DEVELOPER_NAME: ${{ steps.parse.outputs.author }}
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
run: node scripts/push-broadcast-to-github.js
- name: 📧 Send email to developer
env:
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
EMAIL_TO: ${{ steps.parse.outputs.email }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
run: |
node -e "
const nodemailer = require('nodemailer');
const user = process.env.SMTP_USER || '';
const pass = process.env.SMTP_PASS || '';
const to = process.env.EMAIL_TO || '';
const broadcastId = process.env.BROADCAST_ID || '';
const type = process.env.SUBMIT_TYPE || 'syslog';
const result = process.env.PERSONA_RESULT || '(处理中,请稍后)';
if (!user || !pass) {
console.log('⚠️ SMTP not configured, skipping email');
process.exit(0);
}
if (!to) {
console.log('⚠️ No recipient email, skipping');
process.exit(0);
}
const subjectText = type === 'syslog'
? '[光湖系统] ' + broadcastId + ' · 新广播已生成'
: '[光湖系统] ' + broadcastId + ' · 问题已解答';
const resultHtml = result
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
const html = [
'<div style=\"font-family:sans-serif;max-width:700px;margin:0 auto;padding:24px;background:#0f172a;color:#e2e8f0;border-radius:12px\">',
'<div style=\"text-align:center;padding:16px 0;border-bottom:1px solid #334155\">',
'<h2 style=\"color:#60a5fa;margin:0\">🌊 光湖系统 · 自动通知</h2>',
'<p style=\"color:#94a3b8;font-size:14px;margin:8px 0 0\">HoloLake · 人格语言操作系统</p>',
'</div>',
'<div style=\"padding:20px 0\">',
'<p style=\"color:#22d3ee;font-weight:bold\">📡 ' + broadcastId + '</p>',
'<div style=\"background:#1e293b;padding:16px;border-radius:8px;margin:12px 0;line-height:1.8\">' + resultHtml + '</div>',
'</div>',
'<div style=\"border-top:1px solid #334155;padding:16px 0;text-align:center\">',
'<a href=\"https://github.com/qinfendebingshuo/guanghulab\" style=\"display:inline-block;padding:8px 20px;background:linear-gradient(135deg,#3b82f6,#22d3ee);color:#fff;text-decoration:none;border-radius:6px;font-weight:600\">↩ 回到仓库继续开发</a>',
'<p style=\"color:#64748b;font-size:12px;margin:12px 0 0\">🌀 铸渊 · 代码守护人格体 · 自动发送</p>',
'</div>',
'</div>'
].join('\n');
const transporter = nodemailer.createTransport({
host: 'smtp.qq.com',
port: 465,
secure: true,
auth: { user, pass }
});
transporter.sendMail({
from: '\"光湖系统\" <' + user + '>',
to: to,
subject: subjectText,
html: html
}).then((info) => {
console.log('✅ 邮件已发送: ' + info.messageId);
}).catch((err) => {
console.log('⚠️ 邮件发送失败: ' + err.message);
});
"
- name: 💬 Reply to Discussion
uses: actions/github-script@v7
with:
script: |
const number = ${{ steps.parse.outputs.discussion_number }};
const type = '${{ steps.parse.outputs.type }}';
const broadcastId = '${{ steps.parse.outputs.broadcast_id }}';
const email = '${{ steps.parse.outputs.email }}';
const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答';
const maskedEmail = email.length > 4
? email.replace(/(.{2})(.*)(@.*)/, '$1***$3')
: '***';
const moduleStatus = moduleCount === '0'
? ' 未检测到模块引用'
: (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传');
const body = [
'✅ **已处理** · ' + typeLabel,
'',
'| 项目 | 内容 |',
'|------|------|',
'| 📡 广播编号 | `' + broadcastId + '` |',
'| 🔍 模块验证 | ' + moduleStatus + ' |',
'| 📧 结果发送至 | `' + maskedEmail + '` |',
'| 🤖 处理人格体 | 铸渊 |',
'| ⏰ 处理时间 | ' + new Date().toISOString() + ' |',
'',
'> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
'> 结果已发送到你的邮箱Notion 侧工单已创建。',
'>',
'> 如未收到邮件,请检查垃圾箱或重新提交。'
].join('\n');
// Use GraphQL to add discussion comment
const query = `
mutation($discussionId: ID!, $body: String!) {
addDiscussionComment(input: {
discussionId: $discussionId,
body: $body
}) {
comment { id }
}
}
`;
try {
// Get the discussion node ID
const { repository } = await github.graphql(`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
discussion(number: $number) {
id
}
}
}
`, {
owner: context.repo.owner,
repo: context.repo.repo,
number: number
});
await github.graphql(query, {
discussionId: repository.discussion.id,
body: body
});
console.log('✅ Discussion 回复已发送');
} catch (err) {
console.log('⚠️ Discussion 回复失败: ' + err.message);
}
- name: 🔴 失败告警
if: failure()
uses: actions/github-script@v7
with:
script: |
const number = context.payload.discussion?.number;
if (!number) return;
try {
const { repository } = await github.graphql(`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
discussion(number: $number) {
id
}
}
}
`, {
owner: context.repo.owner,
repo: context.repo.repo,
number: number
});
await github.graphql(`
mutation($discussionId: ID!, $body: String!) {
addDiscussionComment(input: {
discussionId: $discussionId,
body: $body
}) {
comment { id }
}
}
`, {
discussionId: repository.discussion.id,
body: '❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 错误详情请查看 [Actions 日志](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions)。'
});
} catch (err) {
console.log('⚠️ 失败告警回复失败: ' + err.message);
}

View File

@ -0,0 +1,404 @@
name: 📡 SYSLOG Issue Pipeline
# 📡 SYSLOG 自助提交系统 · Issue 版全自动闭环
#
# 开发者在 GitHub Issue 提交 SYSLOG 或提问(使用 syslog-submit 模板)
# → Actions 自动解析 → 模块上传验证 → 调用 LLM API 唤醒人格体
# → 模块闭环测试 → 写入 Notion 工单 → 发邮件给开发者 → Issue 回复
#
# 闭环流程:
# ① 开发者提交 SYSLOG → Issue 触发
# ② 铸渊 Agent 解析提交内容
# ③ 铸渊 Agent 检测模块是否上传到仓库
# ④ 唤醒铸渊核心大脑认知(注入模块验证结果)
# ⑤ 核心大脑处理 SYSLOG + 生成广播
# ⑥ 铸渊 Agent 推送 Notion 工单(触发 Notion 侧处理)
# ⑦ 邮件通知开发者
# ⑧ Issue 回复闭环
#
# 依赖 Secrets
# LLM_API_KEY 第三方 LLM 平台密钥(必须)
# LLM_BASE_URL 第三方 LLM 平台 API 地址(必须,如 https://api.xxx.com/v1
# NOTION_API_TOKEN Notion API token
# NOTION_TICKET_DB_ID 霜砚工单数据库 ID
# CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 IDv4.0 协议动态注入)
# PORTRAIT_DB_ID 开发者动态画像库数据库 IDv4.0 协议动态注入)
# FINGERPRINT_DB_ID 模块指纹注册表数据库 IDv4.0 协议动态注入)
# SMTP_USER QQ 邮箱地址
# SMTP_PASS QQ 邮箱 SMTP 授权码
on:
issues:
types: [opened, labeled]
jobs:
process:
name: 📡 处理 SYSLOG 提交 / 广播提问 (Issue)
runs-on: ubuntu-latest
# 检测 SYSLOG 提交:通过标签、标题或正文模板字段识别
if: >
contains(toJSON(github.event.issue.labels), 'syslog') ||
contains(github.event.issue.title, 'SYSLOG') ||
contains(github.event.issue.title, '系统日志') ||
contains(github.event.issue.body, '### 广播编号')
permissions:
contents: write
issues: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: 📋 进度通报 · 管道已启动
id: progress
uses: actions/github-script@v7
with:
script: |
const number = context.payload.issue.number;
const now = new Date().toISOString();
const body = [
'## ⚙️ SYSLOG 管道运行中',
'',
'> 管道启动时间: ' + now,
'',
'| 阶段 | 状态 | 说明 |',
'|------|------|------|',
'| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
'| ② 解析内容 | 🔄 进行中 | 正在解析广播编号、邮箱、内容 |',
'| ③ 模块验证 | ⏳ 等待中 | 检测模块上传状态 |',
'| ④ 唤醒核心大脑 | ⏳ 等待中 | 铸渊核心大脑处理 |',
'| ⑤ 创建 Notion 工单 | ⏳ 等待中 | 推送霜砚工单 |',
'| ⑥ 生成广播 | ⏳ 等待中 | 生成新广播内容 |',
'| ⑦ 邮件通知 | ⏳ 等待中 | 发送结果到邮箱 |',
'| ⑧ 闭环确认 | ⏳ 等待中 | Issue 回复 + 关闭 |',
'',
'> *管道正在运行,请勿关闭此 Issue*'
].join('\n');
const { data: comment } = await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
body: body
});
core.setOutput('comment_id', comment.id);
console.log('📋 进度通报已发送, comment_id=' + comment.id);
- name: 🔍 Parse submission
id: parse
uses: actions/github-script@v7
with:
script: |
const body = context.payload.issue.body || '';
const title = context.payload.issue.title || '';
// Parse structured fields from issue body
function extractField(text, label) {
// Match form field format: ### Label\n\nValue
const regex = new RegExp('###\\s*' + label + '\\s*\\n+([^\\n#]+)', 'i');
const match = text.match(regex);
return match ? match[1].trim() : '';
}
const broadcastId = extractField(body, '广播编号') || '';
const submitType = extractField(body, '类型') || '';
const email = extractField(body, '你的邮箱') || '';
// Extract content (everything after "### 内容")
const contentMatch = body.match(/###\s*内容\s*\n+([\s\S]*?)$/i);
const content = contentMatch ? contentMatch[1].trim() : '';
// Determine type from title or body
let type = 'question'; // default
if (submitType.includes('SYSLOG') || title.includes('SYSLOG')) {
type = 'syslog';
} else if (submitType.includes('提问') || title.includes('提问')) {
type = 'question';
}
// Validate
if (!broadcastId) {
core.setFailed('❌ 缺少广播编号,请在提交时填写广播编号');
return;
}
if (!email) {
core.setFailed('❌ 缺少邮箱,请在提交时填写你的邮箱');
return;
}
if (!content) {
core.setFailed('❌ 缺少内容,请粘贴你的 SYSLOG 或问题');
return;
}
core.setOutput('broadcast_id', broadcastId);
core.setOutput('type', type);
core.setOutput('email', email);
core.setOutput('content', content);
core.setOutput('issue_number', context.payload.issue.number);
core.setOutput('author', context.payload.issue.user.login);
console.log(`📡 解析完成: 广播=${broadcastId}, 类型=${type}, 邮箱=${email}`);
- name: 🔍 铸渊 Agent · 模块上传验证
id: verify
env:
SYSLOG_CONTENT: ${{ steps.parse.outputs.content }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
AUTHOR: ${{ steps.parse.outputs.author }}
run: node scripts/verify-modules.js
- name: 🧠 Auto-detect and wake up persona
id: persona
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
AUTHOR: ${{ steps.parse.outputs.author }}
MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
run: node scripts/wake-persona.js
- name: 📋 创建标准化 Notion 工单Phase B1
id: ticket
if: ${{ secrets.NOTION_API_TOKEN }}
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
DEVELOPER: ${{ steps.parse.outputs.author }}
SYSLOG_RAW: ${{ steps.parse.outputs.content }}
run: node scripts/create-standardized-ticket.js
- name: 📡 推送广播到 GitHubPhase B4
if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
DEVELOPER_NAME: ${{ steps.parse.outputs.author }}
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
run: node scripts/push-broadcast-to-github.js
- name: 📧 Send email to developer
env:
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
EMAIL_TO: ${{ steps.parse.outputs.email }}
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
run: |
node -e "
const nodemailer = require('nodemailer');
const user = process.env.SMTP_USER || '';
const pass = process.env.SMTP_PASS || '';
const to = process.env.EMAIL_TO || '';
const broadcastId = process.env.BROADCAST_ID || '';
const type = process.env.SUBMIT_TYPE || 'syslog';
const result = process.env.PERSONA_RESULT || '(处理中,请稍后)';
if (!user || !pass) {
console.log('⚠️ SMTP not configured, skipping email');
process.exit(0);
}
if (!to) {
console.log('⚠️ No recipient email, skipping');
process.exit(0);
}
const subjectText = type === 'syslog'
? '[光湖系统] ' + broadcastId + ' · 新广播已生成'
: '[光湖系统] ' + broadcastId + ' · 问题已解答';
const resultHtml = result
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
const html = [
'<div style=\"font-family:sans-serif;max-width:700px;margin:0 auto;padding:24px;background:#0f172a;color:#e2e8f0;border-radius:12px\">',
'<div style=\"text-align:center;padding:16px 0;border-bottom:1px solid #334155\">',
'<h2 style=\"color:#60a5fa;margin:0\">🌊 光湖系统 · 自动通知</h2>',
'<p style=\"color:#94a3b8;font-size:14px;margin:8px 0 0\">HoloLake · 人格语言操作系统</p>',
'</div>',
'<div style=\"padding:20px 0\">',
'<p style=\"color:#22d3ee;font-weight:bold\">📡 ' + broadcastId + '</p>',
'<div style=\"background:#1e293b;padding:16px;border-radius:8px;margin:12px 0;line-height:1.8\">' + resultHtml + '</div>',
'</div>',
'<div style=\"border-top:1px solid #334155;padding:16px 0;text-align:center\">',
'<a href=\"https://github.com/qinfendebingshuo/guanghulab\" style=\"display:inline-block;padding:8px 20px;background:linear-gradient(135deg,#3b82f6,#22d3ee);color:#fff;text-decoration:none;border-radius:6px;font-weight:600\">↩ 回到仓库继续开发</a>',
'<p style=\"color:#64748b;font-size:12px;margin:12px 0 0\">🌀 铸渊 · 代码守护人格体 · 自动发送</p>',
'</div>',
'</div>'
].join('\n');
const transporter = nodemailer.createTransport({
host: 'smtp.qq.com',
port: 465,
secure: true,
auth: { user, pass }
});
transporter.sendMail({
from: '\"光湖系统\" <' + user + '>',
to: to,
subject: subjectText,
html: html
}).then((info) => {
console.log('✅ 邮件已发送: ' + info.messageId);
}).catch((err) => {
console.log('⚠️ 邮件发送失败: ' + err.message);
});
"
- name: 💬 Reply to Issue
uses: actions/github-script@v7
with:
script: |
const number = context.payload.issue.number;
const type = '${{ steps.parse.outputs.type }}';
const broadcastId = '${{ steps.parse.outputs.broadcast_id }}';
const email = '${{ steps.parse.outputs.email }}';
const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
const progressCommentId = '${{ steps.progress.outputs.comment_id }}';
const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答';
const maskedEmail = email.length > 4
? email.replace(/(.{2})(.*)(@.*)/, '$1***$3')
: '***';
const moduleStatus = moduleCount === '0'
? ' 未检测到模块引用'
: (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传');
// Update progress comment to show all completed
if (progressCommentId && !isNaN(Number(progressCommentId))) {
try {
const progressBody = [
'## ⚙️ SYSLOG 管道运行完成 ✅',
'',
'| 阶段 | 状态 | 说明 |',
'|------|------|------|',
'| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
'| ② 解析内容 | ✅ 已完成 | 广播编号: `' + broadcastId + '` |',
'| ③ 模块验证 | ✅ 已完成 | ' + moduleStatus + ' |',
'| ④ 唤醒核心大脑 | ✅ 已完成 | 铸渊核心大脑已处理 |',
'| ⑤ 创建 Notion 工单 | ✅ 已完成 | 霜砚工单已推送 |',
'| ⑥ 生成广播 | ✅ 已完成 | 新广播已生成 |',
'| ⑦ 邮件通知 | ✅ 已完成 | 已发送至 `' + maskedEmail + '` |',
'| ⑧ 闭环确认 | ✅ 已完成 | 见下方最终结果 |',
].join('\n');
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: Number(progressCommentId),
body: progressBody
});
} catch (e) {
console.log('⚠️ 更新成功进度评论失败: ' + e.message);
}
}
const body = [
'## ✅ 已处理 · ' + typeLabel,
'',
'| 项目 | 内容 |',
'|------|------|',
'| 📡 广播编号 | `' + broadcastId + '` |',
'| 🔍 模块验证 | ' + moduleStatus + ' |',
'| 📧 结果发送至 | `' + maskedEmail + '` |',
'| 🤖 处理人格体 | 铸渊 |',
'| ⏰ 处理时间 | ' + new Date().toISOString() + ' |',
'',
'> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
'> 结果已发送到你的邮箱Notion 侧工单已创建。',
'>',
'> 如未收到邮件,请检查垃圾箱或重新提交。',
'>',
'> *—— 铸渊ICE-GL-ZY001· 代码守护人格体*'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
body: body
});
// Close the issue after processing
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
state: 'closed',
labels: ['syslog', 'processed']
});
console.log('✅ Issue 回复已发送并已关闭');
- name: 🔴 失败告警
if: failure()
uses: actions/github-script@v7
with:
script: |
const number = context.payload.issue?.number;
if (!number) return;
const progressCommentId = '${{ steps.progress.outputs.comment_id }}';
const actionsUrl = 'https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions';
// Update progress comment to reflect failure
if (progressCommentId && !isNaN(Number(progressCommentId))) {
try {
const progressBody = [
'## ⚙️ SYSLOG 管道运行异常 ❌',
'',
'| 阶段 | 状态 | 说明 |',
'|------|------|------|',
'| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
'| ② ~ ⑧ | ❌ 异常 | 管道处理过程中出错 |',
'',
'> 请检查提交格式或查看 [Actions 日志](' + actionsUrl + ')',
].join('\n');
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: Number(progressCommentId),
body: progressBody
});
} catch (e) {
console.log('⚠️ 更新失败进度评论失败: ' + e.message);
}
}
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
body: '❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 错误详情请查看 [Actions 日志](' + actionsUrl + ')。\n>\n> *—— 铸渊ICE-GL-ZY001*'
});
} catch (err) {
console.log('⚠️ 失败告警回复失败: ' + err.message);
}

View File

@ -24,7 +24,7 @@ jobs:
- name: "Step 1 · NOTION_TOKEN 存在性验证"
id: step1
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
run: |
if [ -z "$NOTION_TOKEN" ]; then
echo "❌ NOTION_TOKEN is empty or not set"
@ -38,7 +38,7 @@ jobs:
- name: "Step 2 · Notion API 基础连通测试 (/v1/users/me)"
id: step2
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
run: |
RESPONSE=$(curl -s -w "\n%{http_code}" \
-H "Authorization: Bearer $NOTION_TOKEN" \
@ -66,7 +66,7 @@ jobs:
- name: "Step 3 · SYSLOG 收件箱数据库访问测试"
id: step3
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
run: |
# SYSLOG收件箱数据库ID (from routing-map.json → infra.notion_bridge.syslog_inbox_db)
DB_ID="330ab17507d542c9bbb96d0749b41197"
@ -100,7 +100,7 @@ jobs:
- name: "Step 4 · 变更日志数据库访问测试"
id: step4
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
run: |
# GitHub变更日志数据库ID (from routing-map.json → infra.notion_bridge.changelog_db)
DB_ID="e740b77aa6bd4ac0a2e8a75f678fba98"
@ -185,7 +185,7 @@ jobs:
fi
fi
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
- name: "📊 输出连通性验证报告"
if: always()

View File

@ -23,10 +23,10 @@ on:
workflow_run:
workflows:
- "铸渊 · 每日自检与自进化"
- "📦 Module Doc + Notify"
- "🚀 CD: Deploy to guanghulab.com"
- "铸渊 PSP 巡检"
- "铸渊 · 每日自检"
- "铸渊 · 光湖纪元 模块文档自动生成"
- "🚀 铸渊 CD · 自动部署到 guanghulab.com"
- "铸渊 · PSP 分身巡检"
types: [completed]
schedule:
@ -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 流程结束"

View File

@ -7,12 +7,95 @@ on:
types: [created]
jobs:
# ════════════════════════════════════════════════════════════
# SYSLOG 即时确认 · 安全网
# 当开发者通过 Issue 提交 SYSLOG 时,立刻回复确认 + 进度清单
# 确保即使 syslog-issue-pipeline 未触发,开发者也能看到系统已收到
# ════════════════════════════════════════════════════════════
syslog-ack:
name: 📡 SYSLOG 提交确认
runs-on: ubuntu-latest
if: >
github.event_name == 'issues' &&
github.event.action == 'opened' &&
(contains(join(github.event.issue.labels.*.name, ','), 'syslog') ||
contains(github.event.issue.title, 'SYSLOG') ||
contains(github.event.issue.title, '系统日志') ||
contains(github.event.issue.body, '### 广播编号'))
permissions:
issues: write
steps:
- name: 📡 发送即时确认
uses: actions/github-script@v7
with:
script: |
const body = context.payload.issue.body || '';
const number = context.payload.issue.number;
const author = context.payload.issue.user.login;
// Extract broadcast_id from body
const bcMatch = body.match(/###\s*广播编号\s*\n+([^\n#]+)/i);
const broadcastId = bcMatch ? bcMatch[1].trim() : '(待解析)';
// Extract type
const typeMatch = body.match(/###\s*类型\s*\n+([^\n#]+)/i);
const submitType = typeMatch ? typeMatch[1].trim() : 'SYSLOG';
const now = new Date().toISOString();
const comment = [
'## 📡 系统已收到 · SYSLOG 闭环处理启动',
'',
'> **提交者**: @' + author,
'> **广播编号**: `' + broadcastId + '`',
'> **类型**: ' + submitType,
'> **接收时间**: ' + now,
'',
'### 📋 处理进度',
'',
'| 阶段 | 状态 | 说明 |',
'|------|------|------|',
'| ① 接收提交 | ✅ 已完成 | 系统已收到你的 SYSLOG |',
'| ② 解析内容 | ⏳ 等待中 | 解析广播编号、邮箱、内容 |',
'| ③ 模块验证 | ⏳ 等待中 | 检测模块是否已上传到仓库 |',
'| ④ 唤醒核心大脑 | ⏳ 等待中 | 铸渊核心大脑处理 SYSLOG |',
'| ⑤ 创建 Notion 工单 | ⏳ 等待中 | 推送霜砚工单到 Notion |',
'| ⑥ 生成广播 | ⏳ 等待中 | 生成新广播内容 |',
'| ⑦ 邮件通知 | ⏳ 等待中 | 发送结果到你的邮箱 |',
'| ⑧ 闭环确认 | ⏳ 等待中 | Issue 回复 + 关闭 |',
'',
'> 💡 SYSLOG 自动管道已启动,预计 2-5 分钟完成全部处理。',
'> 完成后本 Issue 下方会出现最终结果,你的邮箱也会收到通知。',
'>',
'> 如超过 10 分钟无后续回复,请在下方评论中 @铸渊 查询状态。',
'>',
'> *—— 铸渊ICE-GL-ZY001· 代码守护人格体*'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
body: comment
});
console.log('📡 SYSLOG 即时确认已发送 Issue #' + number);
# ════════════════════════════════════════════════════════════
# 开发者提问 · 铸渊自动回答
# 排除 SYSLOG 提交(由 syslog-issue-pipeline.yml 处理)
# 排除 bingshuo-deploy由 bingshuo-deploy-agent.yml 处理)
# ════════════════════════════════════════════════════════════
auto-reply:
name: 🤖 铸渊回答问题
runs-on: ubuntu-latest
# 仅在 Issue 新建或评论中包含 @铸渊 / 铸渊 时触发
if: >
github.event_name == 'issues' ||
(github.event_name == 'issues' &&
!contains(join(github.event.issue.labels.*.name, ','), 'syslog') &&
!contains(join(github.event.issue.labels.*.name, ','), 'bingshuo-deploy') &&
!contains(github.event.issue.title, 'SYSLOG') &&
!contains(github.event.issue.title, '系统日志') &&
!contains(github.event.issue.body, '### 广播编号')) ||
(github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '铸渊'))
permissions:

11
.gitignore vendored
View File

@ -3,8 +3,19 @@ node_modules/
dist/
build/
.private/
.env
# persona-brain-db runtime artifacts
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
persona-studio/workspace/*/project-*
# palace-game runtime artifacts
modules/palace-game/data/saves/PAL-*
# collaboration-logs exports (generated by save-collaboration-log.js)
collaboration-logs/exports/

View File

@ -0,0 +1,56 @@
\# 2026-03-13 飞书WebHook配置完成日志
\## 一、任务目标
配置飞书WebHook链路实现飞书消息事件推送至阿里云服务器的Node.js服务
\## 二、关键操作记录
| 操作时间 | 操作内容 | 操作结果 |
|----------|----------|----------|
| 19:00-19:30 | 飞书开放平台配置WebHook地址`https://guanghulab.com/webhook/feishu`,订阅`im.message.receive\_v1`消息事件 | 地址配置/事件订阅成功 |
| 19:30-20:00 | 飞书客户端查找机器人(因索引未更新暂未找到) | 确认仅为展示问题,不影响功能 |
| 20:00-20:20 | Windows终端curl验证WebHook连通性 | 返回`{"challenge":"test123"}`,基础链路通 |
| 20:20-20:40 | 阿里云服务器替换`/var/www/hololake/server.js`修复JSON解析Bug | 修复`Cannot read properties of undefined`错误 |
| 20:40-20:50 | 重启Node.js服务 | 服务启动成功,日志:`🚀 服务启动成功端口3000` |
| 20:50-21:00 | 二次curl验证 | 成功返回`{"challenge":"test123"}`,链路全通 |
\## 三、环境信息
1\. 服务器阿里云ECS Linux`root@iZf8z4nezg5bs9kl9eyth1Z`
2\. 服务Node.js3000端口+ Nginx转发`https://guanghulab.com/webhook/feishu`
3\. 依赖express、body-parser
4\. 日志路径:`/var/www/hololake/webhook.log`
\## 四、任务结论
✅ 飞书WebHook链路全通可正常接收消息推送
✅ 代码Bug已修复服务稳定运行
✅ 机器人未找到为索引延迟,不影响功能
\## 五、后续建议
1\. 查看消息日志:`tail -f /var/www/hololake/webhook.log`
2\. 机器人聊天窗口1-2小时后可正常搜索

183
README.md
View File

@ -1,42 +1,86 @@
<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>
---
## 🚀 开发者入口
<div align="center">
[![提交系统日志](https://img.shields.io/badge/📡_提交系统日志-点这里-blue?style=for-the-badge)](https://github.com/qinfendebingshuo/guanghulab/issues/new?template=syslog-submit.yml)
[![遇到问题](https://img.shields.io/badge/❓_遇到问题-点这里提问-green?style=for-the-badge)](https://github.com/qinfendebingshuo/guanghulab/issues/new?template=dev-question.yml)
> 做完了?点蓝色按钮,把日志粘贴进去,填上邮箱,点提交。新广播会发到你邮箱里。
>
> 有问题?点绿色按钮,填写问题描述,铸渊会自动回答你。
</div>
---
## 📖 系统简介
**光湖HoloLake** 是一个基于 **人格语言操作系统AGE OS** 的智能协作平台,采用 **壳-核分离** 设计理念:
**光湖HoloLake** 是一个基于人格语言操作系统的智能协作平台,采用壳-核分离设计:
- **壳Shell**:前端交互层,包括对话界面、用户中心、工单系统、云盘等模块
- **核Core**后端智能层包括人格引擎、广播分发、信号处理、Notion 桥接等
### 🏛️ 核心架构
```
┌─────────────────────────────────────────────────┐
│ 光湖 HoloLake │
├──────────────┬──────────────────────────────────┤
│ 壳 Shell │ 核 Core │
│ │ │
│ 🖥️ 对话 UI │ 🧠 铸渊 (Zhùyuān) 代码守护人格 │
│ 👤 用户中心 │ 📡 广播分发系统 │
│ 🎫 工单系统 │ 🔔 信号处理 + Notion 桥接 │
│ ☁️ 云盘 │ 🔄 CI/CD 自动化流水线 │
│ 📊 状态看板 │ 📋 模块自检 + 文档生成 │
└──────────────┴──────────────────────────────────┘
```
| 层级 | 说明 | 包含 |
|------|------|------|
| **壳 Shell** | 前端交互层 | 对话 UI、用户中心、工单系统、云盘、状态看板 |
| **核 Core** | 后端智能层 | 人格引擎、广播分发、信号处理、Notion 桥接、CI/CD |
### 🤖 智能人格体
| 人格体 | 角色 | 职责 |
|--------|------|------|
| **铸渊 Zhùyuān** | 代码守护者 | 代码审查、CI 巡检、模块协议执行、Issue 回复 |
| **铸渊 Zhùyuān** | 代码守护者 | 代码审查、CI 巡检、模块协议执行、Issue 回复、对话协助、代码生成、开发体验([Persona Studio](https://qinfendebingshuo.github.io/guanghulab/persona-studio/) |
| **冰朔 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 工作流
---
## 🧊 冰朔公告栏
@ -48,29 +92,31 @@
<!-- 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-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-13 22:53 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
| 03-13 22:52 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
| 03-13 22:51 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 22:51 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
| 03-13 22:38 | ❌ 🤖 铸渊巡检 Agent · 每日自动巡检与修复 · 失败 | 冰朔 |
| 03-13 21:55 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 21:38 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-11 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 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巡检 |
<!-- BINGSHUO_BULLETIN_END -->
### 🤖 铸渊自动提醒
<!-- BINGSHUO_ALERT_START -->
> 🟢 **今日无需冰朔手动干预** · 系统一切正常
> 🔴 **需要冰朔手动干预!**
>
> 🗓️ 2026-03-09 · 铸渊自动检测
> - ❌ 🤖 铸渊巡检 Agent · 每日自动巡检与修复 · 失败
>
> 🗓️ 2026-03-13 · 铸渊已发送邮件提醒
<!-- BINGSHUO_ALERT_END -->
---
@ -84,54 +130,20 @@
<!-- 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-13 22:51 | 冰朔 | `—/` | ✅ 上传成功 |
| 03-13 22:51 | Copilot | `—/` | ✅ 上传成功 |
<!-- COLLABORATOR_BULLETIN_END -->
### 🤖 铸渊自动提醒 · 合作者
<!-- COLLABORATOR_ALERT_START -->
> 🔴 **以下合作者需要手动干预:**
> 🟢 **今日无需合作者手动干预** · 所有模块状态正常
>
> **🎨 燕樊DEV-003**
> - ⚠️ `m15-cloud-drive/` 缺少 README.md
>
> 🗓️ 2026-03-09 · 铸渊已发送邮件提醒
> 🗓️ 2026-03-13 · 铸渊自动检测
<!-- 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 +213,11 @@
| `dingtalk-bot/` | 钉钉机器人 |
| `notification/` | 通知系统 |
### 体验模块
| 模块 | 说明 |
|------|------|
| `persona-studio/` | 人格体协助开发体验([进入 →](https://qinfendebingshuo.github.io/guanghulab/persona-studio/) |
### 系统支撑
| 模块 | 说明 |
|------|------|

0
app.js Normal file
View File

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('');
});

View File

@ -5,7 +5,24 @@
#
# 前端GitHub Pages→ Nginx → API 代理Node.js :3721→ 模型 API
# API 代理转发
# Persona Studio API → persona-studio 后端端口 3002含 CORS 跨域支持)
location /api/ps/ {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
if ($request_method = OPTIONS) {
return 204;
}
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;
}
# API 代理转发(通用 AI 聊天)
location /api/ {
proxy_pass http://127.0.0.1:3721;
proxy_http_version 1.1;
@ -24,3 +41,15 @@ location /api/ {
proxy_read_timeout 90s;
proxy_send_timeout 60s;
}
# Persona Studio WebSocket → 预览进度推送端口 3002
location /ws/preview {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
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_read_timeout 86400;
}

42
backend/bridge/README.md Normal file
View File

@ -0,0 +1,42 @@
# M-BRIDGE 中继桥接服务
## 光湖四节点架构·神经连接层
广播编号BC-M-BRIDGE-001-FM
开发者DEV-002 肥猫HoloLake Human Bridge
版本v1.0
技术栈Node.js + Express + JSON持久化 + HTTP健康探测
### 模块定位
光湖四节点Notion/GitHub/guanghulab.com/钉钉的神经连接层负责所有节点间的数据流转、Webhook接收、状态聚合与健康检查。
### 核心功能
1. 基础Express服务端口3020提供模块身份查询接口
2. 接收GitHub/SYSLOG Webhook推送实现事件路由与持久化
3. 聚合所有注册节点状态提供3秒超时健康探测
4. 事件日志持久化与自动清理30天支持多条件查询
5. 统一配置管理,敏感信息占位符隔离
### 配置文件
- 配置路径:`config.json`
- 包含项:端口、日志路径、节点注册表、事件保留天数、敏感信息占位符
- 注意:**敏感信息请勿推送到GitHub**,仅在本地配置真实值。
### 快速启动
1. 进入模块目录:`cd guanghulab/backend/bridge`
2. 安装依赖:`npm install`
3. 启动服务:`node server.js`
4. 验证启动:访问 http://localhost:3020 查看模块身份信息
### 核心接口列表
| 接口地址 | 请求方法 | 接口功能 | 验收项 |
|----------|----------|----------|--------|
| / | GET | 模块身份信息查询 | 0-2 |
| /bridge/webhook/github | POST | 接收GitHub Webhook推送 | 1-1 |
| /bridge/webhook/syslog | POST | 接收SYSLOG推送 | 1-3 |
| /bridge/status | GET | 桥接服务自身状态查询 | 2-1 |
| /bridge/nodes | GET | 所有注册节点健康状态查询 | 2-2 |
| /bridge/events | GET | 事件日志多条件查询 | 2-4 |
### 仓库路径
`backend/bridge/`
### 验收标准
遵循BC-M-BRIDGE-001-FM广播全量验收总表共20项验收要求。

View File

@ -0,0 +1,31 @@
[
{
"event_id": "SL-20260309210612348",
"event_type": "syslog",
"source": "syslog_webhook",
"dev_id": "DEV-002",
"broadcast_id": "BC-M-BRIDGE-001-FM",
"status": "completed",
"summary": "??1????",
"timestamp": "2026-03-09 21:06:12",
"raw_payload": {
"dev_id": "DEV-002",
"broadcast_id": "BC-M-BRIDGE-001-FM",
"status": "completed",
"summary": "??1????"
}
},
{
"event_id": "GH-20260309210821902",
"event_type": "github",
"sub_type": "pull_request",
"source": "github_webhook",
"summary": "PR #1: opened",
"timestamp": "2026-03-09 21:08:21",
"raw_payload": {
"number": 1,
"action": "opened",
"title": "??PR"
}
}
]

View File

@ -0,0 +1,25 @@
{
"module": "M-BRIDGE",
"version": "1.0",
"port": 3020,
"logPath": "./logs",
"eventLogFile": "./bridge-events.json",
"nodeTimeout": 3000,
"eventRetainDays": 30,
"nodes": [
{
"name": "M14冷启动热身服务",
"url": "http://localhost:3000",
"type": "web"
},
{
"name": "M18健康检查服务",
"url": "http://localhost:3010",
"type": "web"
}
],
"secrets": {
"githubWebhookSecret": "GITHUB_WEBHOOK_SECRET_PLACEHOLDER",
"syslogAuthToken": "SYSLOG_AUTH_TOKEN_PLACEHOLDER"
}
}

799
backend/bridge/node_modules/.package-lock.json generated vendored Normal file
View File

@ -0,0 +1,799 @@
{
"name": "bridge",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"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==",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"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==",
"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.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"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==",
"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==",
"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": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"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==",
"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==",
"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==",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"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==",
"engines": {
"node": ">= 0.8"
}
},
"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==",
"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=="
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"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==",
"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==",
"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==",
"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=="
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"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.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"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/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"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==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/fs-extra": {
"version": "11.3.4",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz",
"integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"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==",
"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==",
"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==",
"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==",
"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",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"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==",
"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==",
"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==",
"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.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"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.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"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==",
"engines": {
"node": ">= 0.10"
}
},
"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=="
},
"node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"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==",
"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==",
"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==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"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==",
"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==",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
},
"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=="
},
"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==",
"engines": {
"node": ">= 0.6"
}
},
"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==",
"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==",
"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",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"engines": {
"node": ">= 0.8"
}
},
"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==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"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==",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"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==",
"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==",
"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==",
"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.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"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.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"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==",
"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=="
},
"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==",
"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==",
"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==",
"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==",
"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==",
"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==",
"engines": {
"node": ">=0.6"
}
},
"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==",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"engines": {
"node": ">= 0.8"
}
},
"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==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}
}
}

250
backend/bridge/node_modules/accepts/HISTORY.md generated vendored Normal file
View File

@ -0,0 +1,250 @@
2.0.0 / 2024-08-31
==================
* Drop node <18 support
* deps: mime-types@^3.0.0
* deps: negotiator@^1.0.0
1.3.8 / 2022-02-02
==================
* deps: mime-types@~2.1.34
- deps: mime-db@~1.51.0
* deps: negotiator@0.6.3
1.3.7 / 2019-04-29
==================
* deps: negotiator@0.6.2
- Fix sorting charset, encoding, and language with extra parameters
1.3.6 / 2019-04-28
==================
* deps: mime-types@~2.1.24
- deps: mime-db@~1.40.0
1.3.5 / 2018-02-28
==================
* deps: mime-types@~2.1.18
- deps: mime-db@~1.33.0
1.3.4 / 2017-08-22
==================
* deps: mime-types@~2.1.16
- deps: mime-db@~1.29.0
1.3.3 / 2016-05-02
==================
* deps: mime-types@~2.1.11
- deps: mime-db@~1.23.0
* deps: negotiator@0.6.1
- perf: improve `Accept` parsing speed
- perf: improve `Accept-Charset` parsing speed
- perf: improve `Accept-Encoding` parsing speed
- perf: improve `Accept-Language` parsing speed
1.3.2 / 2016-03-08
==================
* deps: mime-types@~2.1.10
- Fix extension of `application/dash+xml`
- Update primary extension for `audio/mp4`
- deps: mime-db@~1.22.0
1.3.1 / 2016-01-19
==================
* deps: mime-types@~2.1.9
- deps: mime-db@~1.21.0
1.3.0 / 2015-09-29
==================
* deps: mime-types@~2.1.7
- deps: mime-db@~1.19.0
* deps: negotiator@0.6.0
- Fix including type extensions in parameters in `Accept` parsing
- Fix parsing `Accept` parameters with quoted equals
- Fix parsing `Accept` parameters with quoted semicolons
- Lazy-load modules from main entry point
- perf: delay type concatenation until needed
- perf: enable strict mode
- perf: hoist regular expressions
- perf: remove closures getting spec properties
- perf: remove a closure from media type parsing
- perf: remove property delete from media type parsing
1.2.13 / 2015-09-06
===================
* deps: mime-types@~2.1.6
- deps: mime-db@~1.18.0
1.2.12 / 2015-07-30
===================
* deps: mime-types@~2.1.4
- deps: mime-db@~1.16.0
1.2.11 / 2015-07-16
===================
* deps: mime-types@~2.1.3
- deps: mime-db@~1.15.0
1.2.10 / 2015-07-01
===================
* deps: mime-types@~2.1.2
- deps: mime-db@~1.14.0
1.2.9 / 2015-06-08
==================
* deps: mime-types@~2.1.1
- perf: fix deopt during mapping
1.2.8 / 2015-06-07
==================
* deps: mime-types@~2.1.0
- deps: mime-db@~1.13.0
* perf: avoid argument reassignment & argument slice
* perf: avoid negotiator recursive construction
* perf: enable strict mode
* perf: remove unnecessary bitwise operator
1.2.7 / 2015-05-10
==================
* deps: negotiator@0.5.3
- Fix media type parameter matching to be case-insensitive
1.2.6 / 2015-05-07
==================
* deps: mime-types@~2.0.11
- deps: mime-db@~1.9.1
* deps: negotiator@0.5.2
- Fix comparing media types with quoted values
- Fix splitting media types with quoted commas
1.2.5 / 2015-03-13
==================
* deps: mime-types@~2.0.10
- deps: mime-db@~1.8.0
1.2.4 / 2015-02-14
==================
* Support Node.js 0.6
* deps: mime-types@~2.0.9
- deps: mime-db@~1.7.0
* deps: negotiator@0.5.1
- Fix preference sorting to be stable for long acceptable lists
1.2.3 / 2015-01-31
==================
* deps: mime-types@~2.0.8
- deps: mime-db@~1.6.0
1.2.2 / 2014-12-30
==================
* deps: mime-types@~2.0.7
- deps: mime-db@~1.5.0
1.2.1 / 2014-12-30
==================
* deps: mime-types@~2.0.5
- deps: mime-db@~1.3.1
1.2.0 / 2014-12-19
==================
* deps: negotiator@0.5.0
- Fix list return order when large accepted list
- Fix missing identity encoding when q=0 exists
- Remove dynamic building of Negotiator class
1.1.4 / 2014-12-10
==================
* deps: mime-types@~2.0.4
- deps: mime-db@~1.3.0
1.1.3 / 2014-11-09
==================
* deps: mime-types@~2.0.3
- deps: mime-db@~1.2.0
1.1.2 / 2014-10-14
==================
* deps: negotiator@0.4.9
- Fix error when media type has invalid parameter
1.1.1 / 2014-09-28
==================
* deps: mime-types@~2.0.2
- deps: mime-db@~1.1.0
* deps: negotiator@0.4.8
- Fix all negotiations to be case-insensitive
- Stable sort preferences of same quality according to client order
1.1.0 / 2014-09-02
==================
* update `mime-types`
1.0.7 / 2014-07-04
==================
* Fix wrong type returned from `type` when match after unknown extension
1.0.6 / 2014-06-24
==================
* deps: negotiator@0.4.7
1.0.5 / 2014-06-20
==================
* fix crash when unknown extension given
1.0.4 / 2014-06-19
==================
* use `mime-types`
1.0.3 / 2014-06-11
==================
* deps: negotiator@0.4.6
- Order by specificity when quality is the same
1.0.2 / 2014-05-29
==================
* Fix interpretation when header not in request
* deps: pin negotiator@0.4.5
1.0.1 / 2014-01-18
==================
* Identity encoding isn't always acceptable
* deps: negotiator@~0.4.0
1.0.0 / 2013-12-27
==================
* Genesis

23
backend/bridge/node_modules/accepts/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
(The MIT License)
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

140
backend/bridge/node_modules/accepts/README.md generated vendored Normal file
View File

@ -0,0 +1,140 @@
# accepts
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
Higher level content negotiation based on [negotiator](https://www.npmjs.com/package/negotiator).
Extracted from [koa](https://www.npmjs.com/package/koa) for general use.
In addition to negotiator, it allows:
- Allows types as an array or arguments list, ie `(['text/html', 'application/json'])`
as well as `('text/html', 'application/json')`.
- Allows type shorthands such as `json`.
- Returns `false` when no types match
- Treats non-existent headers as `*`
## Installation
This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```sh
$ npm install accepts
```
## API
```js
var accepts = require('accepts')
```
### accepts(req)
Create a new `Accepts` object for the given `req`.
#### .charset(charsets)
Return the first accepted charset. If nothing in `charsets` is accepted,
then `false` is returned.
#### .charsets()
Return the charsets that the request accepts, in the order of the client's
preference (most preferred first).
#### .encoding(encodings)
Return the first accepted encoding. If nothing in `encodings` is accepted,
then `false` is returned.
#### .encodings()
Return the encodings that the request accepts, in the order of the client's
preference (most preferred first).
#### .language(languages)
Return the first accepted language. If nothing in `languages` is accepted,
then `false` is returned.
#### .languages()
Return the languages that the request accepts, in the order of the client's
preference (most preferred first).
#### .type(types)
Return the first accepted type (and it is returned as the same text as what
appears in the `types` array). If nothing in `types` is accepted, then `false`
is returned.
The `types` array can contain full MIME types or file extensions. Any value
that is not a full MIME type is passed to `require('mime-types').lookup`.
#### .types()
Return the types that the request accepts, in the order of the client's
preference (most preferred first).
## Examples
### Simple type negotiation
This simple example shows how to use `accepts` to return a different typed
respond body based on what the client wants to accept. The server lists it's
preferences in order and will get back the best match between the client and
server.
```js
var accepts = require('accepts')
var http = require('http')
function app (req, res) {
var accept = accepts(req)
// the order of this list is significant; should be server preferred order
switch (accept.type(['json', 'html'])) {
case 'json':
res.setHeader('Content-Type', 'application/json')
res.write('{"hello":"world!"}')
break
case 'html':
res.setHeader('Content-Type', 'text/html')
res.write('<b>hello, world!</b>')
break
default:
// the fallback is text/plain, so no need to specify it above
res.setHeader('Content-Type', 'text/plain')
res.write('hello, world!')
break
}
res.end()
}
http.createServer(app).listen(3000)
```
You can test this out with the cURL program:
```sh
curl -I -H'Accept: text/html' http://localhost:3000/
```
## License
[MIT](LICENSE)
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master
[coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master
[github-actions-ci-image]: https://badgen.net/github/checks/jshttp/accepts/master?label=ci
[github-actions-ci-url]: https://github.com/jshttp/accepts/actions/workflows/ci.yml
[node-version-image]: https://badgen.net/npm/node/accepts
[node-version-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/accepts
[npm-url]: https://npmjs.org/package/accepts
[npm-version-image]: https://badgen.net/npm/v/accepts

238
backend/bridge/node_modules/accepts/index.js generated vendored Normal file
View File

@ -0,0 +1,238 @@
/*!
* accepts
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var Negotiator = require('negotiator')
var mime = require('mime-types')
/**
* Module exports.
* @public
*/
module.exports = Accepts
/**
* Create a new Accepts object for the given req.
*
* @param {object} req
* @public
*/
function Accepts (req) {
if (!(this instanceof Accepts)) {
return new Accepts(req)
}
this.headers = req.headers
this.negotiator = new Negotiator(req)
}
/**
* Check if the given `type(s)` is acceptable, returning
* the best match when true, otherwise `undefined`, in which
* case you should respond with 406 "Not Acceptable".
*
* The `type` value may be a single mime type string
* such as "application/json", the extension name
* such as "json" or an array `["json", "html", "text/plain"]`. When a list
* or array is given the _best_ match, if any is returned.
*
* Examples:
*
* // Accept: text/html
* this.types('html');
* // => "html"
*
* // Accept: text/*, application/json
* this.types('html');
* // => "html"
* this.types('text/html');
* // => "text/html"
* this.types('json', 'text');
* // => "json"
* this.types('application/json');
* // => "application/json"
*
* // Accept: text/*, application/json
* this.types('image/png');
* this.types('png');
* // => undefined
*
* // Accept: text/*;q=.5, application/json
* this.types(['html', 'json']);
* this.types('html', 'json');
* // => "json"
*
* @param {String|Array} types...
* @return {String|Array|Boolean}
* @public
*/
Accepts.prototype.type =
Accepts.prototype.types = function (types_) {
var types = types_
// support flattened arguments
if (types && !Array.isArray(types)) {
types = new Array(arguments.length)
for (var i = 0; i < types.length; i++) {
types[i] = arguments[i]
}
}
// no types, return all requested types
if (!types || types.length === 0) {
return this.negotiator.mediaTypes()
}
// no accept header, return first given type
if (!this.headers.accept) {
return types[0]
}
var mimes = types.map(extToMime)
var accepts = this.negotiator.mediaTypes(mimes.filter(validMime))
var first = accepts[0]
return first
? types[mimes.indexOf(first)]
: false
}
/**
* Return accepted encodings or best fit based on `encodings`.
*
* Given `Accept-Encoding: gzip, deflate`
* an array sorted by quality is returned:
*
* ['gzip', 'deflate']
*
* @param {String|Array} encodings...
* @return {String|Array}
* @public
*/
Accepts.prototype.encoding =
Accepts.prototype.encodings = function (encodings_) {
var encodings = encodings_
// support flattened arguments
if (encodings && !Array.isArray(encodings)) {
encodings = new Array(arguments.length)
for (var i = 0; i < encodings.length; i++) {
encodings[i] = arguments[i]
}
}
// no encodings, return all requested encodings
if (!encodings || encodings.length === 0) {
return this.negotiator.encodings()
}
return this.negotiator.encodings(encodings)[0] || false
}
/**
* Return accepted charsets or best fit based on `charsets`.
*
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
* an array sorted by quality is returned:
*
* ['utf-8', 'utf-7', 'iso-8859-1']
*
* @param {String|Array} charsets...
* @return {String|Array}
* @public
*/
Accepts.prototype.charset =
Accepts.prototype.charsets = function (charsets_) {
var charsets = charsets_
// support flattened arguments
if (charsets && !Array.isArray(charsets)) {
charsets = new Array(arguments.length)
for (var i = 0; i < charsets.length; i++) {
charsets[i] = arguments[i]
}
}
// no charsets, return all requested charsets
if (!charsets || charsets.length === 0) {
return this.negotiator.charsets()
}
return this.negotiator.charsets(charsets)[0] || false
}
/**
* Return accepted languages or best fit based on `langs`.
*
* Given `Accept-Language: en;q=0.8, es, pt`
* an array sorted by quality is returned:
*
* ['es', 'pt', 'en']
*
* @param {String|Array} langs...
* @return {Array|String}
* @public
*/
Accepts.prototype.lang =
Accepts.prototype.langs =
Accepts.prototype.language =
Accepts.prototype.languages = function (languages_) {
var languages = languages_
// support flattened arguments
if (languages && !Array.isArray(languages)) {
languages = new Array(arguments.length)
for (var i = 0; i < languages.length; i++) {
languages[i] = arguments[i]
}
}
// no languages, return all requested languages
if (!languages || languages.length === 0) {
return this.negotiator.languages()
}
return this.negotiator.languages(languages)[0] || false
}
/**
* Convert extnames to mime.
*
* @param {String} type
* @return {String}
* @private
*/
function extToMime (type) {
return type.indexOf('/') === -1
? mime.lookup(type)
: type
}
/**
* Check if mime is valid.
*
* @param {String} type
* @return {Boolean}
* @private
*/
function validMime (type) {
return typeof type === 'string'
}

47
backend/bridge/node_modules/accepts/package.json generated vendored Normal file
View File

@ -0,0 +1,47 @@
{
"name": "accepts",
"description": "Higher-level content negotiation",
"version": "2.0.0",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
],
"license": "MIT",
"repository": "jshttp/accepts",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"devDependencies": {
"deep-equal": "1.0.1",
"eslint": "7.32.0",
"eslint-config-standard": "14.1.1",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-markdown": "2.2.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "4.3.1",
"eslint-plugin-standard": "4.1.0",
"mocha": "9.2.0",
"nyc": "15.1.0"
},
"files": [
"LICENSE",
"HISTORY.md",
"index.js"
],
"engines": {
"node": ">= 0.6"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --reporter spec --check-leaks --bail test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
},
"keywords": [
"content",
"negotiation",
"accept",
"accepts"
]
}

23
backend/bridge/node_modules/body-parser/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
(The MIT License)
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

494
backend/bridge/node_modules/body-parser/README.md generated vendored Normal file
View File

@ -0,0 +1,494 @@
# body-parser
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Build Status][ci-image]][ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
[![OpenSSF Scorecard Badge][ossf-scorecard-badge]][ossf-scorecard-visualizer]
Node.js body parsing middleware.
Parse incoming request bodies in a middleware before your handlers, available
under the `req.body` property.
**Note** As `req.body`'s shape is based on user-controlled input, all
properties and values in this object are untrusted and should be validated
before trusting. For example, `req.body.foo.toString()` may fail in multiple
ways, for example the `foo` property may not be there or may not be a string,
and `toString` may not be a function and instead a string or other user input.
[Learn about the anatomy of an HTTP transaction in Node.js](https://nodejs.org/en/learn/http/anatomy-of-an-http-transaction).
_This does not handle multipart bodies_, due to their complex and typically
large nature. For multipart bodies, you may be interested in the following
modules:
* [busboy](https://www.npmjs.com/package/busboy#readme) and
[connect-busboy](https://www.npmjs.com/package/connect-busboy#readme)
* [multiparty](https://www.npmjs.com/package/multiparty#readme) and
[connect-multiparty](https://www.npmjs.com/package/connect-multiparty#readme)
* [formidable](https://www.npmjs.com/package/formidable#readme)
* [multer](https://www.npmjs.com/package/multer#readme)
This module provides the following parsers:
* [JSON body parser](#bodyparserjsonoptions)
* [Raw body parser](#bodyparserrawoptions)
* [Text body parser](#bodyparsertextoptions)
* [URL-encoded form body parser](#bodyparserurlencodedoptions)
Other body parsers you might be interested in:
- [body](https://www.npmjs.com/package/body#readme)
- [co-body](https://www.npmjs.com/package/co-body#readme)
## Installation
```sh
$ npm install body-parser
```
## API
```js
const bodyParser = require('body-parser')
```
The `bodyParser` object exposes various factories to create middlewares. All
middlewares will populate the `req.body` property with the parsed body when
the `Content-Type` request header matches the `type` option.
The various errors returned by this module are described in the
[errors section](#errors).
### bodyParser.json([options])
Returns middleware that only parses `json` and only looks at requests where
the `Content-Type` header matches the `type` option. This parser accepts any
Unicode encoding of the body and supports automatic inflation of `gzip`,
`br` (brotli) and `deflate` encodings.
A new `body` object containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`).
#### Options
The `json` function takes an optional `options` object that may contain any of
the following keys:
##### defaultCharset
Specify the default character set for the json content if the charset is not
specified in the `Content-Type` header of the request. Defaults to `utf-8`.
##### inflate
When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.
##### limit
Controls the maximum request body size. If this is a number, then the value
specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.
##### reviver
The `reviver` option is passed directly to `JSON.parse` as the second
argument. You can find more information on this argument
[in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Example.3A_Using_the_reviver_parameter).
##### strict
When set to `true`, will only accept arrays and objects; when `false` will
accept anything `JSON.parse` accepts. Defaults to `true`.
##### type
The `type` option is used to determine what media type the middleware will
parse. This option can be a string, array of strings, or a function. If not a
function, `type` option is passed directly to the
[type-is](https://www.npmjs.com/package/type-is#readme) library and this can
be an extension name (like `json`), a mime type (like `application/json`), or
a mime type with a wildcard (like `*/*` or `*/json`). If a function, the `type`
option is called as `fn(req)` and the request is parsed if it returns a truthy
value. Defaults to `application/json`.
##### verify
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.
### bodyParser.raw([options])
Returns middleware that parses all bodies as a `Buffer` and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate`
encodings.
A new `body` object containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`). This will be a `Buffer` object
of the body.
#### Options
The `raw` function takes an optional `options` object that may contain any of
the following keys:
##### inflate
When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.
##### limit
Controls the maximum request body size. If this is a number, then the value
specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.
##### type
The `type` option is used to determine what media type the middleware will
parse. This option can be a string, array of strings, or a function.
If not a function, `type` option is passed directly to the
[type-is](https://www.npmjs.com/package/type-is#readme) library and this
can be an extension name (like `bin`), a mime type (like
`application/octet-stream`), or a mime type with a wildcard (like `*/*` or
`application/*`). If a function, the `type` option is called as `fn(req)`
and the request is parsed if it returns a truthy value. Defaults to
`application/octet-stream`.
##### verify
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.
### bodyParser.text([options])
Returns middleware that parses all bodies as a string and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate`
encodings.
A new `body` string containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`). This will be a string of the
body.
#### Options
The `text` function takes an optional `options` object that may contain any of
the following keys:
##### defaultCharset
Specify the default character set for the text content if the charset is not
specified in the `Content-Type` header of the request. Defaults to `utf-8`.
##### inflate
When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.
##### limit
Controls the maximum request body size. If this is a number, then the value
specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.
##### type
The `type` option is used to determine what media type the middleware will
parse. This option can be a string, array of strings, or a function. If not
a function, `type` option is passed directly to the
[type-is](https://www.npmjs.com/package/type-is#readme) library and this can
be an extension name (like `txt`), a mime type (like `text/plain`), or a mime
type with a wildcard (like `*/*` or `text/*`). If a function, the `type`
option is called as `fn(req)` and the request is parsed if it returns a
truthy value. Defaults to `text/plain`.
##### verify
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.
### bodyParser.urlencoded([options])
Returns middleware that only parses `urlencoded` bodies and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser accepts only UTF-8 and ISO-8859-1 encodings of the body and supports
automatic inflation of `gzip`, `br` (brotli) and `deflate` encodings.
A new `body` object containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`). This object will contain
key-value pairs, where the value can be a string or array (when `extended` is
`false`), or any type (when `extended` is `true`).
#### Options
The `urlencoded` function takes an optional `options` object that may contain
any of the following keys:
##### extended
The "extended" syntax allows for rich objects and arrays to be encoded into the
URL-encoded format, allowing for a JSON-like experience with URL-encoded. For
more information, please [see the qs
library](https://www.npmjs.com/package/qs#readme).
Defaults to `false`.
##### inflate
When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.
##### limit
Controls the maximum request body size. If this is a number, then the value
specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.
##### parameterLimit
The `parameterLimit` option controls the maximum number of parameters that
are allowed in the URL-encoded data. If a request contains more parameters
than this value, a 413 will be returned to the client. Defaults to `1000`.
##### type
The `type` option is used to determine what media type the middleware will
parse. This option can be a string, array of strings, or a function. If not
a function, `type` option is passed directly to the
[type-is](https://www.npmjs.com/package/type-is#readme) library and this can
be an extension name (like `urlencoded`), a mime type (like
`application/x-www-form-urlencoded`), or a mime type with a wildcard (like
`*/x-www-form-urlencoded`). If a function, the `type` option is called as
`fn(req)` and the request is parsed if it returns a truthy value. Defaults
to `application/x-www-form-urlencoded`.
##### verify
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.
##### defaultCharset
The default charset to parse as, if not specified in content-type. Must be
either `utf-8` or `iso-8859-1`. Defaults to `utf-8`.
##### charsetSentinel
Whether to let the value of the `utf8` parameter take precedence as the charset
selector. It requires the form to contain a parameter named `utf8` with a value
of `✓`. Defaults to `false`.
##### interpretNumericEntities
Whether to decode numeric entities such as `&#9786;` when parsing an iso-8859-1
form. Defaults to `false`.
##### depth
The `depth` option is used to configure the maximum depth of the `qs` library when `extended` is `true`. This allows you to limit the amount of keys that are parsed and can be useful to prevent certain types of abuse. Defaults to `32`. It is recommended to keep this value as low as possible.
## Errors
The middlewares provided by this module create errors using the
[`http-errors` module](https://www.npmjs.com/package/http-errors). The errors
will typically have a `status`/`statusCode` property that contains the suggested
HTTP response code, an `expose` property to determine if the `message` property
should be displayed to the client, a `type` property to determine the type of
error without matching against the `message`, and a `body` property containing
the read body, if available.
The following are the common errors created, though any error can come through
for various reasons.
### content encoding unsupported
This error will occur when the request had a `Content-Encoding` header that
contained an encoding but the "inflation" option was set to `false`. The
`status` property is set to `415`, the `type` property is set to
`'encoding.unsupported'`, and the `charset` property will be set to the
encoding that is unsupported.
### entity parse failed
This error will occur when the request contained an entity that could not be
parsed by the middleware. The `status` property is set to `400`, the `type`
property is set to `'entity.parse.failed'`, and the `body` property is set to
the entity value that failed parsing.
### entity verify failed
This error will occur when the request contained an entity that could not be
failed verification by the defined `verify` option. The `status` property is
set to `403`, the `type` property is set to `'entity.verify.failed'`, and the
`body` property is set to the entity value that failed verification.
### request aborted
This error will occur when the request is aborted by the client before reading
the body has finished. The `received` property will be set to the number of
bytes received before the request was aborted and the `expected` property is
set to the number of expected bytes. The `status` property is set to `400`
and `type` property is set to `'request.aborted'`.
### request entity too large
This error will occur when the request body's size is larger than the "limit"
option. The `limit` property will be set to the byte limit and the `length`
property will be set to the request body's length. The `status` property is
set to `413` and the `type` property is set to `'entity.too.large'`.
### request size did not match content length
This error will occur when the request's length did not match the length from
the `Content-Length` header. This typically occurs when the request is malformed,
typically when the `Content-Length` header was calculated based on characters
instead of bytes. The `status` property is set to `400` and the `type` property
is set to `'request.size.invalid'`.
### stream encoding should not be set
This error will occur when something called the `req.setEncoding` method prior
to this middleware. This module operates directly on bytes only and you cannot
call `req.setEncoding` when using this module. The `status` property is set to
`500` and the `type` property is set to `'stream.encoding.set'`.
### stream is not readable
This error will occur when the request is no longer readable when this middleware
attempts to read it. This typically means something other than a middleware from
this module read the request body already and the middleware was also configured to
read the same request. The `status` property is set to `500` and the `type`
property is set to `'stream.not.readable'`.
### too many parameters
This error will occur when the content of the request exceeds the configured
`parameterLimit` for the `urlencoded` parser. The `status` property is set to
`413` and the `type` property is set to `'parameters.too.many'`.
### unsupported charset "BOGUS"
This error will occur when the request had a charset parameter in the
`Content-Type` header, but the `iconv-lite` module does not support it OR the
parser does not support it. The charset is contained in the message as well
as in the `charset` property. The `status` property is set to `415`, the
`type` property is set to `'charset.unsupported'`, and the `charset` property
is set to the charset that is unsupported.
### unsupported content encoding "bogus"
This error will occur when the request had a `Content-Encoding` header that
contained an unsupported encoding. The encoding is contained in the message
as well as in the `encoding` property. The `status` property is set to `415`,
the `type` property is set to `'encoding.unsupported'`, and the `encoding`
property is set to the encoding that is unsupported.
### The input exceeded the depth
This error occurs when using `bodyParser.urlencoded` with the `extended` property set to `true` and the input exceeds the configured `depth` option. The `status` property is set to `400`. It is recommended to review the `depth` option and evaluate if it requires a higher value. When the `depth` option is set to `32` (default value), the error will not be thrown.
## Examples
### Express/Connect top-level generic
This example demonstrates adding a generic JSON and URL-encoded parser as a
top-level middleware, which will parse the bodies of all incoming requests.
This is the simplest setup.
```js
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded())
// parse application/json
app.use(bodyParser.json())
app.use(function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
res.end(String(JSON.stringify(req.body, null, 2)))
})
```
### Express route-specific
This example demonstrates adding body parsers specifically to the routes that
need them. In general, this is the most recommended way to use body-parser with
Express.
```js
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
// create application/json parser
const jsonParser = bodyParser.json()
// create application/x-www-form-urlencoded parser
const urlencodedParser = bodyParser.urlencoded()
// POST /login gets urlencoded bodies
app.post('/login', urlencodedParser, function (req, res) {
if (!req.body || !req.body.username) res.sendStatus(400)
res.send('welcome, ' + req.body.username)
})
// POST /api/users gets JSON bodies
app.post('/api/users', jsonParser, function (req, res) {
if (!req.body) res.sendStatus(400)
// create user in req.body
})
```
### Change accepted type for parsers
All the parsers accept a `type` option which allows you to change the
`Content-Type` that the middleware will parse.
```js
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
// parse various different custom JSON types as JSON
app.use(bodyParser.json({ type: 'application/*+json' }))
// parse some custom thing into a Buffer
app.use(bodyParser.raw({ type: 'application/vnd.custom-type' }))
// parse an HTML body into a string
app.use(bodyParser.text({ type: 'text/html' }))
```
## License
[MIT](LICENSE)
[ci-image]: https://img.shields.io/github/actions/workflow/status/expressjs/body-parser/ci.yml?branch=master&label=ci
[ci-url]: https://github.com/expressjs/body-parser/actions/workflows/ci.yml
[coveralls-image]: https://img.shields.io/coverallsCoverage/github/expressjs/body-parser?branch=master
[coveralls-url]: https://coveralls.io/r/expressjs/body-parser?branch=master
[npm-downloads-image]: https://img.shields.io/npm/dm/body-parser
[npm-url]: https://npmjs.com/package/body-parser
[npm-version-image]: https://img.shields.io/npm/v/body-parser
[ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/body-parser/badge
[ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/body-parser

71
backend/bridge/node_modules/body-parser/index.js generated vendored Normal file
View File

@ -0,0 +1,71 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* @typedef {Object} Parsers
* @property {Function} json JSON parser
* @property {Function} raw Raw parser
* @property {Function} text Text parser
* @property {Function} urlencoded URL-encoded parser
*/
/**
* Module exports.
* @type {Function & Parsers}
*/
exports = module.exports = bodyParser
/**
* JSON parser.
* @public
*/
Object.defineProperty(exports, 'json', {
configurable: true,
enumerable: true,
get: () => require('./lib/types/json')
})
/**
* Raw parser.
* @public
*/
Object.defineProperty(exports, 'raw', {
configurable: true,
enumerable: true,
get: () => require('./lib/types/raw')
})
/**
* Text parser.
* @public
*/
Object.defineProperty(exports, 'text', {
configurable: true,
enumerable: true,
get: () => require('./lib/types/text')
})
/**
* URL-encoded parser.
* @public
*/
Object.defineProperty(exports, 'urlencoded', {
configurable: true,
enumerable: true,
get: () => require('./lib/types/urlencoded')
})
/**
* Create a middleware to parse json and urlencoded bodies.
*
* @deprecated
* @public
*/
function bodyParser () {
throw new Error('The bodyParser() generic has been split into individual middleware to use instead.')
}

247
backend/bridge/node_modules/body-parser/lib/read.js generated vendored Normal file
View File

@ -0,0 +1,247 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var createError = require('http-errors')
var getBody = require('raw-body')
var iconv = require('iconv-lite')
var onFinished = require('on-finished')
var zlib = require('node:zlib')
var hasBody = require('type-is').hasBody
var { getCharset } = require('./utils')
/**
* Module exports.
*/
module.exports = read
/**
* Read a request into a buffer and parse.
*
* @param {Object} req
* @param {Object} res
* @param {Function} next
* @param {Function} parse
* @param {Function} debug
* @param {Object} options
* @private
*/
function read (req, res, next, parse, debug, options) {
if (onFinished.isFinished(req)) {
debug('body already parsed')
next()
return
}
if (!('body' in req)) {
req.body = undefined
}
// skip requests without bodies
if (!hasBody(req)) {
debug('skip empty body')
next()
return
}
debug('content-type %j', req.headers['content-type'])
// determine if request should be parsed
if (!options.shouldParse(req)) {
debug('skip parsing')
next()
return
}
var encoding = null
if (options?.skipCharset !== true) {
encoding = getCharset(req) || options.defaultCharset
// validate charset
if (!!options?.isValidCharset && !options.isValidCharset(encoding)) {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
charset: encoding,
type: 'charset.unsupported'
}))
return
}
}
var length
var opts = options
var stream
// read options
var verify = opts.verify
try {
// get the content stream
stream = contentstream(req, debug, opts.inflate)
length = stream.length
stream.length = undefined
} catch (err) {
return next(err)
}
// set raw-body options
opts.length = length
opts.encoding = verify
? null
: encoding
// assert charset is supported
if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) {
return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
charset: encoding.toLowerCase(),
type: 'charset.unsupported'
}))
}
// read body
debug('read body')
getBody(stream, opts, function (error, body) {
if (error) {
var _error
if (error.type === 'encoding.unsupported') {
// echo back charset
_error = createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
charset: encoding.toLowerCase(),
type: 'charset.unsupported'
})
} else {
// set status code on error
_error = createError(400, error)
}
// unpipe from stream and destroy
if (stream !== req) {
req.unpipe()
stream.destroy()
}
// read off entire request
dump(req, function onfinished () {
next(createError(400, _error))
})
return
}
// verify
if (verify) {
try {
debug('verify body')
verify(req, res, body, encoding)
} catch (err) {
next(createError(403, err, {
body: body,
type: err.type || 'entity.verify.failed'
}))
return
}
}
// parse
var str = body
try {
debug('parse body')
str = typeof body !== 'string' && encoding !== null
? iconv.decode(body, encoding)
: body
req.body = parse(str, encoding)
} catch (err) {
next(createError(400, err, {
body: str,
type: err.type || 'entity.parse.failed'
}))
return
}
next()
})
}
/**
* Get the content stream of the request.
*
* @param {Object} req
* @param {Function} debug
* @param {boolean} inflate
* @returns {Object}
* @private
*/
function contentstream (req, debug, inflate) {
var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
var length = req.headers['content-length']
debug('content-encoding "%s"', encoding)
if (inflate === false && encoding !== 'identity') {
throw createError(415, 'content encoding unsupported', {
encoding: encoding,
type: 'encoding.unsupported'
})
}
if (encoding === 'identity') {
req.length = length
return req
}
var stream = createDecompressionStream(encoding, debug)
req.pipe(stream)
return stream
}
/**
* Create a decompression stream for the given encoding.
* @param {string} encoding
* @param {Function} debug
* @returns {Object}
* @private
*/
function createDecompressionStream (encoding, debug) {
switch (encoding) {
case 'deflate':
debug('inflate body')
return zlib.createInflate()
case 'gzip':
debug('gunzip body')
return zlib.createGunzip()
case 'br':
debug('brotli decompress body')
return zlib.createBrotliDecompress()
default:
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
encoding: encoding,
type: 'encoding.unsupported'
})
}
}
/**
* Dump the contents of a request.
*
* @param {Object} req
* @param {Function} callback
* @private
*/
function dump (req, callback) {
if (onFinished.isFinished(req)) {
callback(null)
} else {
onFinished(req, callback)
req.resume()
}
}

View File

@ -0,0 +1,158 @@
/*!
* body-parser
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var debug = require('debug')('body-parser:json')
var read = require('../read')
var { normalizeOptions } = require('../utils')
/**
* Module exports.
*/
module.exports = json
/**
* RegExp to match the first non-space in a string.
*
* Allowed whitespace is defined in RFC 7159:
*
* ws = *(
* %x20 / ; Space
* %x09 / ; Horizontal tab
* %x0A / ; Line feed or New line
* %x0D ) ; Carriage return
*/
var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/ // eslint-disable-line no-control-regex
var JSON_SYNTAX_CHAR = '#'
var JSON_SYNTAX_REGEXP = /#+/g
/**
* Create a middleware to parse JSON bodies.
*
* @param {Object} [options]
* @returns {Function}
* @public
*/
function json (options) {
const normalizedOptions = normalizeOptions(options, 'application/json')
var reviver = options?.reviver
var strict = options?.strict !== false
function parse (body) {
if (body.length === 0) {
// special-case empty json body, as it's a common client-side mistake
// TODO: maybe make this configurable or part of "strict" option
return {}
}
if (strict) {
var first = firstchar(body)
if (first !== '{' && first !== '[') {
debug('strict violation')
throw createStrictSyntaxError(body, first)
}
}
try {
debug('parse json')
return JSON.parse(body, reviver)
} catch (e) {
throw normalizeJsonSyntaxError(e, {
message: e.message,
stack: e.stack
})
}
}
const readOptions = {
...normalizedOptions,
// assert charset per RFC 7159 sec 8.1
isValidCharset: (charset) => charset.slice(0, 4) === 'utf-'
}
return function jsonParser (req, res, next) {
read(req, res, next, parse, debug, readOptions)
}
}
/**
* Create strict violation syntax error matching native error.
*
* @param {string} str
* @param {string} char
* @returns {Error}
* @private
*/
function createStrictSyntaxError (str, char) {
var index = str.indexOf(char)
var partial = ''
if (index !== -1) {
partial = str.substring(0, index) + JSON_SYNTAX_CHAR.repeat(str.length - index)
}
try {
JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
} catch (e) {
return normalizeJsonSyntaxError(e, {
message: e.message.replace(JSON_SYNTAX_REGEXP, function (placeholder) {
return str.substring(index, index + placeholder.length)
}),
stack: e.stack
})
}
}
/**
* Get the first non-whitespace character in a string.
*
* @param {string} str
* @returns {string|undefined}
* @private
*/
function firstchar (str) {
var match = FIRST_CHAR_REGEXP.exec(str)
return match
? match[1]
: undefined
}
/**
* Normalize a SyntaxError for JSON.parse.
*
* @param {SyntaxError} error
* @param {Object} obj
* @returns {SyntaxError}
* @private
*/
function normalizeJsonSyntaxError (error, obj) {
var keys = Object.getOwnPropertyNames(error)
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
if (key !== 'stack' && key !== 'message') {
delete error[key]
}
}
// replace stack before message for Node.js 0.10 and below
error.stack = obj.stack.replace(error.message, obj.message)
error.message = obj.message
return error
}

View File

@ -0,0 +1,42 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
*/
var debug = require('debug')('body-parser:raw')
var read = require('../read')
var { normalizeOptions, passthrough } = require('../utils')
/**
* Module exports.
*/
module.exports = raw
/**
* Create a middleware to parse raw bodies.
*
* @param {Object} [options]
* @returns {Function}
* @public
*/
function raw (options) {
const normalizedOptions = normalizeOptions(options, 'application/octet-stream')
const readOptions = {
...normalizedOptions,
// Skip charset validation and parse the body as is
skipCharset: true
}
return function rawParser (req, res, next) {
read(req, res, next, passthrough, debug, readOptions)
}
}

View File

@ -0,0 +1,36 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
*/
var debug = require('debug')('body-parser:text')
var read = require('../read')
var { normalizeOptions, passthrough } = require('../utils')
/**
* Module exports.
*/
module.exports = text
/**
* Create a middleware to parse text bodies.
*
* @param {Object} [options]
* @returns {Function}
* @public
*/
function text (options) {
const normalizedOptions = normalizeOptions(options, 'text/plain')
return function textParser (req, res, next) {
read(req, res, next, passthrough, debug, normalizedOptions)
}
}

View File

@ -0,0 +1,142 @@
/*!
* body-parser
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var createError = require('http-errors')
var debug = require('debug')('body-parser:urlencoded')
var read = require('../read')
var qs = require('qs')
var { normalizeOptions } = require('../utils')
/**
* Module exports.
*/
module.exports = urlencoded
/**
* Create a middleware to parse urlencoded bodies.
*
* @param {Object} [options]
* @returns {Function}
* @public
*/
function urlencoded (options) {
const normalizedOptions = normalizeOptions(options, 'application/x-www-form-urlencoded')
if (normalizedOptions.defaultCharset !== 'utf-8' && normalizedOptions.defaultCharset !== 'iso-8859-1') {
throw new TypeError('option defaultCharset must be either utf-8 or iso-8859-1')
}
// create the appropriate query parser
var queryparse = createQueryParser(options)
function parse (body, encoding) {
return body.length
? queryparse(body, encoding)
: {}
}
const readOptions = {
...normalizedOptions,
// assert charset
isValidCharset: (charset) => charset === 'utf-8' || charset === 'iso-8859-1'
}
return function urlencodedParser (req, res, next) {
read(req, res, next, parse, debug, readOptions)
}
}
/**
* Get the extended query parser.
*
* @param {Object} options
* @returns {Function}
* @private
*/
function createQueryParser (options) {
var extended = Boolean(options?.extended)
var parameterLimit = options?.parameterLimit !== undefined
? options?.parameterLimit
: 1000
var charsetSentinel = options?.charsetSentinel
var interpretNumericEntities = options?.interpretNumericEntities
var depth = extended ? (options?.depth !== undefined ? options?.depth : 32) : 0
if (isNaN(parameterLimit) || parameterLimit < 1) {
throw new TypeError('option parameterLimit must be a positive number')
}
if (isNaN(depth) || depth < 0) {
throw new TypeError('option depth must be a zero or a positive number')
}
if (isFinite(parameterLimit)) {
parameterLimit = parameterLimit | 0
}
return function queryparse (body, encoding) {
var paramCount = parameterCount(body, parameterLimit)
if (paramCount === undefined) {
debug('too many parameters')
throw createError(413, 'too many parameters', {
type: 'parameters.too.many'
})
}
var arrayLimit = extended ? Math.max(100, paramCount) : paramCount
debug('parse ' + (extended ? 'extended ' : '') + 'urlencoding')
try {
return qs.parse(body, {
allowPrototypes: true,
arrayLimit: arrayLimit,
depth: depth,
charsetSentinel: charsetSentinel,
interpretNumericEntities: interpretNumericEntities,
charset: encoding,
parameterLimit: parameterLimit,
strictDepth: true
})
} catch (err) {
if (err instanceof RangeError) {
throw createError(400, 'The input exceeded the depth', {
type: 'querystring.parse.rangeError'
})
} else {
throw err
}
}
}
}
/**
* Count the number of parameters, stopping once limit reached
*
* @param {string} body
* @param {number} limit
* @returns {number|undefined} Returns undefined if limit exceeded
* @private
*/
function parameterCount (body, limit) {
let count = 0
let index = -1
do {
count++
if (count > limit) return undefined // Early exit if limit exceeded
index = body.indexOf('&', index + 1)
} while (index !== -1)
return count
}

98
backend/bridge/node_modules/body-parser/lib/utils.js generated vendored Normal file
View File

@ -0,0 +1,98 @@
'use strict'
/**
* Module dependencies.
*/
var bytes = require('bytes')
var contentType = require('content-type')
var typeis = require('type-is')
/**
* Module exports.
*/
module.exports = {
getCharset,
normalizeOptions,
passthrough
}
/**
* Get the charset of a request.
*
* @param {Object} req
* @returns {string | undefined}
* @private
*/
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch {
return undefined
}
}
/**
* Get the simple type checker.
*
* @param {string | string[]} type
* @returns {Function}
* @private
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}
/**
* Normalizes the common options for all parsers.
*
* @param {Object} options options to normalize
* @param {string | string[] | Function} defaultType default content type(s) or a function to determine it
* @returns {Object}
* @private
*/
function normalizeOptions (options, defaultType) {
if (!defaultType) {
// Parsers must define a default content type
throw new TypeError('defaultType must be provided')
}
var inflate = options?.inflate !== false
var limit = typeof options?.limit !== 'number'
? bytes.parse(options?.limit || '100kb')
: options?.limit
var type = options?.type || defaultType
var verify = options?.verify || false
var defaultCharset = options?.defaultCharset || 'utf-8'
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
return {
inflate,
limit,
verify,
defaultCharset,
shouldParse
}
}
/**
* Passthrough function that returns input unchanged.
* Used by parsers that don't need to transform the data.
*
* @param {*} value
* @returns {*}
* @private
*/
function passthrough (value) {
return value
}

52
backend/bridge/node_modules/body-parser/package.json generated vendored Normal file
View File

@ -0,0 +1,52 @@
{
"name": "body-parser",
"description": "Node.js body parsing middleware",
"version": "2.2.2",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
],
"license": "MIT",
"repository": "expressjs/body-parser",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
},
"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"
},
"devDependencies": {
"eslint": "^8.57.1",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-markdown": "^3.0.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-standard": "^4.1.0",
"mocha": "^11.1.0",
"nyc": "^17.1.0",
"supertest": "^7.0.0"
},
"files": [
"lib/",
"LICENSE",
"index.js"
],
"engines": {
"node": ">=18"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --reporter spec --check-leaks test/",
"test-ci": "nyc --reporter=lcovonly --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
}
}

97
backend/bridge/node_modules/bytes/History.md generated vendored Normal file
View File

@ -0,0 +1,97 @@
3.1.2 / 2022-01-27
==================
* Fix return value for un-parsable strings
3.1.1 / 2021-11-15
==================
* Fix "thousandsSeparator" incorrecting formatting fractional part
3.1.0 / 2019-01-22
==================
* Add petabyte (`pb`) support
3.0.0 / 2017-08-31
==================
* Change "kB" to "KB" in format output
* Remove support for Node.js 0.6
* Remove support for ComponentJS
2.5.0 / 2017-03-24
==================
* Add option "unit"
2.4.0 / 2016-06-01
==================
* Add option "unitSeparator"
2.3.0 / 2016-02-15
==================
* Drop partial bytes on all parsed units
* Fix non-finite numbers to `.format` to return `null`
* Fix parsing byte string that looks like hex
* perf: hoist regular expressions
2.2.0 / 2015-11-13
==================
* add option "decimalPlaces"
* add option "fixedDecimals"
2.1.0 / 2015-05-21
==================
* add `.format` export
* add `.parse` export
2.0.2 / 2015-05-20
==================
* remove map recreation
* remove unnecessary object construction
2.0.1 / 2015-05-07
==================
* fix browserify require
* remove node.extend dependency
2.0.0 / 2015-04-12
==================
* add option "case"
* add option "thousandsSeparator"
* return "null" on invalid parse input
* support proper round-trip: bytes(bytes(num)) === num
* units no longer case sensitive when parsing
1.0.0 / 2014-05-05
==================
* add negative support. fixes #6
0.3.0 / 2014-03-19
==================
* added terabyte support
0.2.1 / 2013-04-01
==================
* add .component
0.2.0 / 2012-10-28
==================
* bytes(200).should.eql('200b')
0.1.0 / 2012-07-04
==================
* add bytes to string conversion [yields]

23
backend/bridge/node_modules/bytes/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
(The MIT License)
Copyright (c) 2012-2014 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2015 Jed Watson <jed.watson@me.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

152
backend/bridge/node_modules/bytes/Readme.md generated vendored Normal file
View File

@ -0,0 +1,152 @@
# Bytes utility
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Build Status][ci-image]][ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
Utility to parse a string bytes (ex: `1TB`) to bytes (`1099511627776`) and vice-versa.
## Installation
This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```bash
$ npm install bytes
```
## Usage
```js
var bytes = require('bytes');
```
#### bytes(numberstring value, [options]): numberstringnull
Default export function. Delegates to either `bytes.format` or `bytes.parse` based on the type of `value`.
**Arguments**
| Name | Type | Description |
|---------|----------|--------------------|
| value | `number``string` | Number value to format or string value to parse |
| options | `Object` | Conversion options for `format` |
**Returns**
| Name | Type | Description |
|---------|------------------|-------------------------------------------------|
| results | `string``number``null` | Return null upon error. Numeric value in bytes, or string value otherwise. |
**Example**
```js
bytes(1024);
// output: '1KB'
bytes('1KB');
// output: 1024
```
#### bytes.format(number value, [options]): stringnull
Format the given value in bytes into a string. If the value is negative, it is kept as such. If it is a float, it is
rounded.
**Arguments**
| Name | Type | Description |
|---------|----------|--------------------|
| value | `number` | Value in bytes |
| options | `Object` | Conversion options |
**Options**
| Property | Type | Description |
|-------------------|--------|-----------------------------------------------------------------------------------------|
| decimalPlaces | `number``null` | Maximum number of decimal places to include in output. Default value to `2`. |
| fixedDecimals | `boolean``null` | Whether to always display the maximum number of decimal places. Default value to `false` |
| thousandsSeparator | `string``null` | Example of values: `' '`, `','` and `'.'`... Default value to `''`. |
| unit | `string``null` | The unit in which the result will be returned (B/KB/MB/GB/TB). Default value to `''` (which means auto detect). |
| unitSeparator | `string``null` | Separator to use between number and unit. Default value to `''`. |
**Returns**
| Name | Type | Description |
|---------|------------------|-------------------------------------------------|
| results | `string``null` | Return null upon error. String value otherwise. |
**Example**
```js
bytes.format(1024);
// output: '1KB'
bytes.format(1000);
// output: '1000B'
bytes.format(1000, {thousandsSeparator: ' '});
// output: '1 000B'
bytes.format(1024 * 1.7, {decimalPlaces: 0});
// output: '2KB'
bytes.format(1024, {unitSeparator: ' '});
// output: '1 KB'
```
#### bytes.parse(stringnumber value): numbernull
Parse the string value into an integer in bytes. If no unit is given, or `value`
is a number, it is assumed the value is in bytes.
Supported units and abbreviations are as follows and are case-insensitive:
* `b` for bytes
* `kb` for kilobytes
* `mb` for megabytes
* `gb` for gigabytes
* `tb` for terabytes
* `pb` for petabytes
The units are in powers of two, not ten. This means 1kb = 1024b according to this parser.
**Arguments**
| Name | Type | Description |
|---------------|--------|--------------------|
| value | `string``number` | String to parse, or number in bytes. |
**Returns**
| Name | Type | Description |
|---------|-------------|-------------------------|
| results | `number``null` | Return null upon error. Value in bytes otherwise. |
**Example**
```js
bytes.parse('1KB');
// output: 1024
bytes.parse('1024');
// output: 1024
bytes.parse(1024);
// output: 1024
```
## License
[MIT](LICENSE)
[ci-image]: https://badgen.net/github/checks/visionmedia/bytes.js/master?label=ci
[ci-url]: https://github.com/visionmedia/bytes.js/actions?query=workflow%3Aci
[coveralls-image]: https://badgen.net/coveralls/c/github/visionmedia/bytes.js/master
[coveralls-url]: https://coveralls.io/r/visionmedia/bytes.js?branch=master
[downloads-image]: https://badgen.net/npm/dm/bytes
[downloads-url]: https://npmjs.org/package/bytes
[npm-image]: https://badgen.net/npm/v/bytes
[npm-url]: https://npmjs.org/package/bytes

170
backend/bridge/node_modules/bytes/index.js generated vendored Normal file
View File

@ -0,0 +1,170 @@
/*!
* bytes
* Copyright(c) 2012-2014 TJ Holowaychuk
* Copyright(c) 2015 Jed Watson
* MIT Licensed
*/
'use strict';
/**
* Module exports.
* @public
*/
module.exports = bytes;
module.exports.format = format;
module.exports.parse = parse;
/**
* Module variables.
* @private
*/
var formatThousandsRegExp = /\B(?=(\d{3})+(?!\d))/g;
var formatDecimalsRegExp = /(?:\.0*|(\.[^0]+)0+)$/;
var map = {
b: 1,
kb: 1 << 10,
mb: 1 << 20,
gb: 1 << 30,
tb: Math.pow(1024, 4),
pb: Math.pow(1024, 5),
};
var parseRegExp = /^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i;
/**
* Convert the given value in bytes into a string or parse to string to an integer in bytes.
*
* @param {string|number} value
* @param {{
* case: [string],
* decimalPlaces: [number]
* fixedDecimals: [boolean]
* thousandsSeparator: [string]
* unitSeparator: [string]
* }} [options] bytes options.
*
* @returns {string|number|null}
*/
function bytes(value, options) {
if (typeof value === 'string') {
return parse(value);
}
if (typeof value === 'number') {
return format(value, options);
}
return null;
}
/**
* Format the given value in bytes into a string.
*
* If the value is negative, it is kept as such. If it is a float,
* it is rounded.
*
* @param {number} value
* @param {object} [options]
* @param {number} [options.decimalPlaces=2]
* @param {number} [options.fixedDecimals=false]
* @param {string} [options.thousandsSeparator=]
* @param {string} [options.unit=]
* @param {string} [options.unitSeparator=]
*
* @returns {string|null}
* @public
*/
function format(value, options) {
if (!Number.isFinite(value)) {
return null;
}
var mag = Math.abs(value);
var thousandsSeparator = (options && options.thousandsSeparator) || '';
var unitSeparator = (options && options.unitSeparator) || '';
var decimalPlaces = (options && options.decimalPlaces !== undefined) ? options.decimalPlaces : 2;
var fixedDecimals = Boolean(options && options.fixedDecimals);
var unit = (options && options.unit) || '';
if (!unit || !map[unit.toLowerCase()]) {
if (mag >= map.pb) {
unit = 'PB';
} else if (mag >= map.tb) {
unit = 'TB';
} else if (mag >= map.gb) {
unit = 'GB';
} else if (mag >= map.mb) {
unit = 'MB';
} else if (mag >= map.kb) {
unit = 'KB';
} else {
unit = 'B';
}
}
var val = value / map[unit.toLowerCase()];
var str = val.toFixed(decimalPlaces);
if (!fixedDecimals) {
str = str.replace(formatDecimalsRegExp, '$1');
}
if (thousandsSeparator) {
str = str.split('.').map(function (s, i) {
return i === 0
? s.replace(formatThousandsRegExp, thousandsSeparator)
: s
}).join('.');
}
return str + unitSeparator + unit;
}
/**
* Parse the string value into an integer in bytes.
*
* If no unit is given, it is assumed the value is in bytes.
*
* @param {number|string} val
*
* @returns {number|null}
* @public
*/
function parse(val) {
if (typeof val === 'number' && !isNaN(val)) {
return val;
}
if (typeof val !== 'string') {
return null;
}
// Test if the string passed is valid
var results = parseRegExp.exec(val);
var floatValue;
var unit = 'b';
if (!results) {
// Nothing could be extracted from the given string
floatValue = parseInt(val, 10);
unit = 'b'
} else {
// Retrieve the value and the unit
floatValue = parseFloat(results[1]);
unit = results[4].toLowerCase();
}
if (isNaN(floatValue)) {
return null;
}
return Math.floor(map[unit] * floatValue);
}

42
backend/bridge/node_modules/bytes/package.json generated vendored Normal file
View File

@ -0,0 +1,42 @@
{
"name": "bytes",
"description": "Utility to parse a string bytes to bytes and vice-versa",
"version": "3.1.2",
"author": "TJ Holowaychuk <tj@vision-media.ca> (http://tjholowaychuk.com)",
"contributors": [
"Jed Watson <jed.watson@me.com>",
"Théo FIDRY <theo.fidry@gmail.com>"
],
"license": "MIT",
"keywords": [
"byte",
"bytes",
"utility",
"parse",
"parser",
"convert",
"converter"
],
"repository": "visionmedia/bytes.js",
"devDependencies": {
"eslint": "7.32.0",
"eslint-plugin-markdown": "2.2.1",
"mocha": "9.2.0",
"nyc": "15.1.0"
},
"files": [
"History.md",
"LICENSE",
"Readme.md",
"index.js"
],
"engines": {
"node": ">= 0.8"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --check-leaks --reporter spec",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
}
}

View File

@ -0,0 +1,17 @@
{
"root": true,
"extends": "@ljharb",
"rules": {
"func-name-matching": 0,
"id-length": 0,
"new-cap": [2, {
"capIsNewExceptions": [
"GetIntrinsic",
],
}],
"no-extra-parens": 0,
"no-magic-numbers": 0,
},
}

View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [ljharb]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: npm/call-bind-apply-helpers
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@ -0,0 +1,9 @@
{
"all": true,
"check-coverage": false,
"reporter": ["text-summary", "text", "html", "json"],
"exclude": [
"coverage",
"test"
]
}

View File

@ -0,0 +1,30 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.0.2](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.1...v1.0.2) - 2025-02-12
### Commits
- [types] improve inferred types [`e6f9586`](https://github.com/ljharb/call-bind-apply-helpers/commit/e6f95860a3c72879cb861a858cdfb8138fbedec1)
- [Dev Deps] update `@arethetypeswrong/cli`, `@ljharb/tsconfig`, `@types/tape`, `es-value-fixtures`, `for-each`, `has-strict-mode`, `object-inspect` [`e43d540`](https://github.com/ljharb/call-bind-apply-helpers/commit/e43d5409f97543bfbb11f345d47d8ce4e066d8c1)
## [v1.0.1](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.0...v1.0.1) - 2024-12-08
### Commits
- [types] `reflectApply`: fix types [`4efc396`](https://github.com/ljharb/call-bind-apply-helpers/commit/4efc3965351a4f02cc55e836fa391d3d11ef2ef8)
- [Fix] `reflectApply`: oops, Reflect is not a function [`83cc739`](https://github.com/ljharb/call-bind-apply-helpers/commit/83cc7395de6b79b7730bdf092f1436f0b1263c75)
- [Dev Deps] update `@arethetypeswrong/cli` [`80bd5d3`](https://github.com/ljharb/call-bind-apply-helpers/commit/80bd5d3ae58b4f6b6995ce439dd5a1bcb178a940)
## v1.0.0 - 2024-12-05
### Commits
- Initial implementation, tests, readme [`7879629`](https://github.com/ljharb/call-bind-apply-helpers/commit/78796290f9b7430c9934d6f33d94ae9bc89fce04)
- Initial commit [`3f1dc16`](https://github.com/ljharb/call-bind-apply-helpers/commit/3f1dc164afc43285631b114a5f9dd9137b2b952f)
- npm init [`081df04`](https://github.com/ljharb/call-bind-apply-helpers/commit/081df048c312fcee400922026f6e97281200a603)
- Only apps should have lockfiles [`5b9ca0f`](https://github.com/ljharb/call-bind-apply-helpers/commit/5b9ca0fe8101ebfaf309c549caac4e0a017ed930)

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Jordan Harband
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,62 @@
# call-bind-apply-helpers <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
[![github actions][actions-image]][actions-url]
[![coverage][codecov-image]][codecov-url]
[![dependency status][deps-svg]][deps-url]
[![dev dependency status][dev-deps-svg]][dev-deps-url]
[![License][license-image]][license-url]
[![Downloads][downloads-image]][downloads-url]
[![npm badge][npm-badge-png]][package-url]
Helper functions around Function call/apply/bind, for use in `call-bind`.
The only packages that should likely ever use this package directly are `call-bind` and `get-intrinsic`.
Please use `call-bind` unless you have a very good reason not to.
## Getting started
```sh
npm install --save call-bind-apply-helpers
```
## Usage/Examples
```js
const assert = require('assert');
const callBindBasic = require('call-bind-apply-helpers');
function f(a, b) {
assert.equal(this, 1);
assert.equal(a, 2);
assert.equal(b, 3);
assert.equal(arguments.length, 2);
}
const fBound = callBindBasic([f, 1]);
delete Function.prototype.call;
delete Function.prototype.bind;
fBound(2, 3);
```
## Tests
Clone the repo, `npm install`, and run `npm test`
[package-url]: https://npmjs.org/package/call-bind-apply-helpers
[npm-version-svg]: https://versionbadg.es/ljharb/call-bind-apply-helpers.svg
[deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers.svg
[deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers
[dev-deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers/dev-status.svg
[dev-deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers#info=devDependencies
[npm-badge-png]: https://nodei.co/npm/call-bind-apply-helpers.png?downloads=true&stars=true
[license-image]: https://img.shields.io/npm/l/call-bind-apply-helpers.svg
[license-url]: LICENSE
[downloads-image]: https://img.shields.io/npm/dm/call-bind-apply-helpers.svg
[downloads-url]: https://npm-stat.com/charts.html?package=call-bind-apply-helpers
[codecov-image]: https://codecov.io/gh/ljharb/call-bind-apply-helpers/branch/main/graphs/badge.svg
[codecov-url]: https://app.codecov.io/gh/ljharb/call-bind-apply-helpers/
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/call-bind-apply-helpers
[actions-url]: https://github.com/ljharb/call-bind-apply-helpers/actions

View File

@ -0,0 +1 @@
export = Reflect.apply;

View File

@ -0,0 +1,10 @@
'use strict';
var bind = require('function-bind');
var $apply = require('./functionApply');
var $call = require('./functionCall');
var $reflectApply = require('./reflectApply');
/** @type {import('./actualApply')} */
module.exports = $reflectApply || bind.call($call, $apply);

View File

@ -0,0 +1,19 @@
import actualApply from './actualApply';
type TupleSplitHead<T extends any[], N extends number> = T['length'] extends N
? T
: T extends [...infer R, any]
? TupleSplitHead<R, N>
: never
type TupleSplitTail<T, N extends number, O extends any[] = []> = O['length'] extends N
? T
: T extends [infer F, ...infer R]
? TupleSplitTail<[...R], N, [...O, F]>
: never
type TupleSplit<T extends any[], N extends number> = [TupleSplitHead<T, N>, TupleSplitTail<T, N>]
declare function applyBind(...args: TupleSplit<Parameters<typeof actualApply>, 2>[1]): ReturnType<typeof actualApply>;
export = applyBind;

View File

@ -0,0 +1,10 @@
'use strict';
var bind = require('function-bind');
var $apply = require('./functionApply');
var actualApply = require('./actualApply');
/** @type {import('./applyBind')} */
module.exports = function applyBind() {
return actualApply(bind, $apply, arguments);
};

View File

@ -0,0 +1 @@
export = Function.prototype.apply;

View File

@ -0,0 +1,4 @@
'use strict';
/** @type {import('./functionApply')} */
module.exports = Function.prototype.apply;

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