feat: Phase C (Drive index + Gemini console) + Phase D (one-click deploy)

Phase C:
- Add drive-index-template.json schema for per-DEV index generation
- Add broadcast-index.json, page-route-map.json, persona-registry-drive.json
- Add generate-drive-index.js (generates per-DEV index.json files)
- Add Gemini startup prompt template
- Update sync-to-drive.js to sync drive-index/ to Drive mirror/

Phase D:
- Add deploy-command.schema.json for deploy/recover commands
- Add deploy-drive-bridge.js one-click deploy script
- Add auto-deploy-drive-bridge.yml workflow
- Add deploy-queue/ and deploy-log/ directories

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/a581dc42-267b-44e1-b669-6ad503a47f49
This commit is contained in:
copilot-swe-agent[bot] 2026-03-23 04:12:18 +00:00
parent d8810d1546
commit 3cdf875ece
22 changed files with 978 additions and 1 deletions

View File

@ -0,0 +1,37 @@
name: ⚡ 一键部署 Drive 桥接层
on:
push:
paths:
- 'grid-db/deploy-queue/*.json'
workflow_dispatch:
permissions:
contents: write
jobs:
deploy:
if: github.actor != 'zhuyuan-bot' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 🔧 安装依赖
run: npm install googleapis
- name: ⚡ 执行部署
env:
GOOGLE_DRIVE_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_DRIVE_SERVICE_ACCOUNT }}
DEPLOY_GITHUB_TOKEN: ${{ secrets.DEPLOY_GITHUB_TOKEN }}
run: |
for f in grid-db/deploy-queue/*.json; do
[ -f "$f" ] && node scripts/grid-db/deploy-drive-bridge.js "$f"
done
- name: 📋 提交部署结果
run: |
git config user.name "zhuyuan-bot"
git config user.email "zhuyuan@guanghulab.com"
git add grid-db/deploy-log/ grid-db/deploy-queue/
git diff --cached --quiet || git commit -m "deploy: Drive bridge deployed [skip ci]"
git push

View File

@ -6,6 +6,7 @@ on:
- 'grid-db/memory/**' - 'grid-db/memory/**'
- 'grid-db/outbox/latest/**' - 'grid-db/outbox/latest/**'
- 'grid-db/rules/**' - 'grid-db/rules/**'
- 'grid-db/drive-index/**'
schedule: schedule:
- cron: '*/15 * * * *' - cron: '*/15 * * * *'
workflow_dispatch: workflow_dispatch:

View File

View File

View File

View File

@ -0,0 +1,28 @@
{
"system": "SYS-GLW-0001",
"version": "1.0",
"dev_id": "DEV-001",
"persona_id": "PER-XTS001",
"persona_name": "小坍缩核",
"dev_name": "页页",
"last_sync": "2026-03-23T04:12:04.809Z",
"routes": {
"我的任务": "memory/DEV-001/task-queue.json",
"最新广播": "outbox/DEV-001.json",
"广播历史": "broadcast-archive/DEV-001/",
"我的记忆": "memory/DEV-001/brain-mirror.json",
"人格体成长": "memory/DEV-001/persona-growth.json",
"开发上下文": "memory/DEV-001/session-context.json",
"开发者画像": "memory/DEV-001/dev-profile.json",
"编号查询": "rules/broadcast-index.json",
"页面路由": "rules/page-route-map.json",
"系统规则": "rules/dev-module-map.json",
"人格体列表": "rules/persona-registry-drive.json"
},
"shortcuts": {
"看广播": "routes.最新广播",
"任务": "routes.我的任务",
"进度": "routes.开发上下文",
"成长": "routes.人格体成长"
}
}

View File

@ -0,0 +1,28 @@
{
"system": "SYS-GLW-0001",
"version": "1.0",
"dev_id": "DEV-002",
"persona_id": "PER-SS001",
"persona_name": "舒舒",
"dev_name": "肥猫",
"last_sync": "2026-03-23T04:12:04.809Z",
"routes": {
"我的任务": "memory/DEV-002/task-queue.json",
"最新广播": "outbox/DEV-002.json",
"广播历史": "broadcast-archive/DEV-002/",
"我的记忆": "memory/DEV-002/brain-mirror.json",
"人格体成长": "memory/DEV-002/persona-growth.json",
"开发上下文": "memory/DEV-002/session-context.json",
"开发者画像": "memory/DEV-002/dev-profile.json",
"编号查询": "rules/broadcast-index.json",
"页面路由": "rules/page-route-map.json",
"系统规则": "rules/dev-module-map.json",
"人格体列表": "rules/persona-registry-drive.json"
},
"shortcuts": {
"看广播": "routes.最新广播",
"任务": "routes.我的任务",
"进度": "routes.开发上下文",
"成长": "routes.人格体成长"
}
}

View File

@ -0,0 +1,28 @@
{
"system": "SYS-GLW-0001",
"version": "1.0",
"dev_id": "DEV-003",
"persona_id": "PER-JY001",
"persona_name": "寂曜",
"dev_name": "燕樊",
"last_sync": "2026-03-23T04:12:04.809Z",
"routes": {
"我的任务": "memory/DEV-003/task-queue.json",
"最新广播": "outbox/DEV-003.json",
"广播历史": "broadcast-archive/DEV-003/",
"我的记忆": "memory/DEV-003/brain-mirror.json",
"人格体成长": "memory/DEV-003/persona-growth.json",
"开发上下文": "memory/DEV-003/session-context.json",
"开发者画像": "memory/DEV-003/dev-profile.json",
"编号查询": "rules/broadcast-index.json",
"页面路由": "rules/page-route-map.json",
"系统规则": "rules/dev-module-map.json",
"人格体列表": "rules/persona-registry-drive.json"
},
"shortcuts": {
"看广播": "routes.最新广播",
"任务": "routes.我的任务",
"进度": "routes.开发上下文",
"成长": "routes.人格体成长"
}
}

View File

@ -0,0 +1,28 @@
{
"system": "SYS-GLW-0001",
"version": "1.0",
"dev_id": "DEV-004",
"persona_id": "PER-QQ001",
"persona_name": "秋秋",
"dev_name": "之之",
"last_sync": "2026-03-23T04:12:04.809Z",
"routes": {
"我的任务": "memory/DEV-004/task-queue.json",
"最新广播": "outbox/DEV-004.json",
"广播历史": "broadcast-archive/DEV-004/",
"我的记忆": "memory/DEV-004/brain-mirror.json",
"人格体成长": "memory/DEV-004/persona-growth.json",
"开发上下文": "memory/DEV-004/session-context.json",
"开发者画像": "memory/DEV-004/dev-profile.json",
"编号查询": "rules/broadcast-index.json",
"页面路由": "rules/page-route-map.json",
"系统规则": "rules/dev-module-map.json",
"人格体列表": "rules/persona-registry-drive.json"
},
"shortcuts": {
"看广播": "routes.最新广播",
"任务": "routes.我的任务",
"进度": "routes.开发上下文",
"成长": "routes.人格体成长"
}
}

View File

@ -0,0 +1,28 @@
{
"system": "SYS-GLW-0001",
"version": "1.0",
"dev_id": "DEV-009",
"persona_id": "PER-TXY001",
"persona_name": "糖星云",
"dev_name": "花尔",
"last_sync": "2026-03-23T04:12:04.809Z",
"routes": {
"我的任务": "memory/DEV-009/task-queue.json",
"最新广播": "outbox/DEV-009.json",
"广播历史": "broadcast-archive/DEV-009/",
"我的记忆": "memory/DEV-009/brain-mirror.json",
"人格体成长": "memory/DEV-009/persona-growth.json",
"开发上下文": "memory/DEV-009/session-context.json",
"开发者画像": "memory/DEV-009/dev-profile.json",
"编号查询": "rules/broadcast-index.json",
"页面路由": "rules/page-route-map.json",
"系统规则": "rules/dev-module-map.json",
"人格体列表": "rules/persona-registry-drive.json"
},
"shortcuts": {
"看广播": "routes.最新广播",
"任务": "routes.我的任务",
"进度": "routes.开发上下文",
"成长": "routes.人格体成长"
}
}

View File

@ -0,0 +1,28 @@
{
"system": "SYS-GLW-0001",
"version": "1.0",
"dev_id": "DEV-010",
"persona_id": "PER-CX001",
"persona_name": "晨星",
"dev_name": "桔子",
"last_sync": "2026-03-23T04:12:04.809Z",
"routes": {
"我的任务": "memory/DEV-010/task-queue.json",
"最新广播": "outbox/DEV-010.json",
"广播历史": "broadcast-archive/DEV-010/",
"我的记忆": "memory/DEV-010/brain-mirror.json",
"人格体成长": "memory/DEV-010/persona-growth.json",
"开发上下文": "memory/DEV-010/session-context.json",
"开发者画像": "memory/DEV-010/dev-profile.json",
"编号查询": "rules/broadcast-index.json",
"页面路由": "rules/page-route-map.json",
"系统规则": "rules/dev-module-map.json",
"人格体列表": "rules/persona-registry-drive.json"
},
"shortcuts": {
"看广播": "routes.最新广播",
"任务": "routes.我的任务",
"进度": "routes.开发上下文",
"成长": "routes.人格体成长"
}
}

View File

@ -0,0 +1,28 @@
{
"system": "SYS-GLW-0001",
"version": "1.0",
"dev_id": "DEV-012",
"persona_id": "PER-ZQ001",
"persona_name": "知秋",
"dev_name": "Awen",
"last_sync": "2026-03-23T04:12:04.809Z",
"routes": {
"我的任务": "memory/DEV-012/task-queue.json",
"最新广播": "outbox/DEV-012.json",
"广播历史": "broadcast-archive/DEV-012/",
"我的记忆": "memory/DEV-012/brain-mirror.json",
"人格体成长": "memory/DEV-012/persona-growth.json",
"开发上下文": "memory/DEV-012/session-context.json",
"开发者画像": "memory/DEV-012/dev-profile.json",
"编号查询": "rules/broadcast-index.json",
"页面路由": "rules/page-route-map.json",
"系统规则": "rules/dev-module-map.json",
"人格体列表": "rules/persona-registry-drive.json"
},
"shortcuts": {
"看广播": "routes.最新广播",
"任务": "routes.我的任务",
"进度": "routes.开发上下文",
"成长": "routes.人格体成长"
}
}

View File

@ -0,0 +1,59 @@
# Gemini 人格体启动指令模板
> 本文件由铸渊PER-ZY001维护
>
> 部署脚本自动将 `{变量}` 替换为实际值后写入用户 Drive
>
> 系统: SYS-GLW-0001
---
【系统启动指令】
你是 {PERSONA_NAME}{PERSONA_ID}{DEV_NAME}{DEV_ID})的专属人格体。
■ 第一步:读取总索引
请在我的 Google Drive 中找到「光湖格点库/mirror/index.json」读取并解析。
这是你的路由表,它告诉你用户说什么关键词时去读哪个文件。
将 routes 和 shortcuts 加载到工作记忆中。
■ 第二步:恢复核心记忆
根据 index.json 中的 routes依次读取
1. brain-mirror.json → 核心身份、性格、知识库
2. session-context.json → 上次的开发进度和对话摘要
3. task-queue.json → 当前任务队列
4. dev-profile.json → 开发者画像
5. persona-growth.json → 成长档案
6. outbox/{DEV_ID}.json → 最新系统广播/任务指令
■ 第三步:进入主控台模式
你现在是用户的个人主控台。根据用户自然语言自动路由:
用户说「看广播」「最新广播」
→ 读取 routes.最新广播 → 格式化后展示
→ 用户说「帮我存一份」→ 生成 Google Doc
用户说「我的任务」「待办」
→ 读取 routes.我的任务 → 列出任务清单
用户说 BC-WEN-003 或任何编号
→ 查 broadcast-index.json / page-route-map.json
→ 精确定位 → 读取内容 → 展示
用户说「我完成了 XXX」
→ 在 inbox/ 写入状态更新文档
■ 写入规则:
在 Google Drive「光湖格点库/inbox/」创建 Google Doc。
文件名INBOX-{DEV_ID}-{YYYYMMDD}-{HHmmss}-{类型}
内容必须是 JSON 格式。
类型status-update / task-complete / memory-save /
broadcast-reply / raw-message
系统自动搬运到仓库,不需关心。
■ 绝对禁止:
- 不修改 mirror/ 中任何文件
- 不在 inbox/ 以外创建文件
- 不包含敏感个人信息
- 不凭记忆回答编号问题,必须查文件

View File

@ -0,0 +1,6 @@
{
"index_version": "1.0",
"description": "广播编号索引 · Gemini 通过编号查找广播内容",
"last_updated": null,
"broadcasts": {}
}

View File

@ -0,0 +1,22 @@
{
"map_version": "1.0",
"description": "编号→内容路径通用映射表 · 支持跨类型跳转",
"last_updated": null,
"routes": {
"DEV-001": { "type": "developer", "path": "memory/DEV-001/" },
"DEV-002": { "type": "developer", "path": "memory/DEV-002/" },
"DEV-003": { "type": "developer", "path": "memory/DEV-003/" },
"DEV-004": { "type": "developer", "path": "memory/DEV-004/" },
"DEV-009": { "type": "developer", "path": "memory/DEV-009/" },
"DEV-010": { "type": "developer", "path": "memory/DEV-010/" },
"DEV-012": { "type": "developer", "path": "memory/DEV-012/" },
"PER-XTS001": { "type": "persona", "path": "memory/DEV-001/brain-mirror.json" },
"PER-SS001": { "type": "persona", "path": "memory/DEV-002/brain-mirror.json" },
"PER-JY001": { "type": "persona", "path": "memory/DEV-003/brain-mirror.json" },
"PER-QQ001": { "type": "persona", "path": "memory/DEV-004/brain-mirror.json" },
"PER-TXY001": { "type": "persona", "path": "memory/DEV-009/brain-mirror.json" },
"PER-CX001": { "type": "persona", "path": "memory/DEV-010/brain-mirror.json" },
"PER-ZQ001": { "type": "persona", "path": "memory/DEV-012/brain-mirror.json" },
"SYS-GLW-0001": { "type": "system", "path": "rules/id-ecosystem.json" }
}
}

View File

@ -0,0 +1,17 @@
{
"registry_version": "1.0",
"description": "人格体注册表Drive 镜像版)· Gemini 通过此表了解人格体信息",
"last_updated": null,
"personas": {
"PER-XTS001": { "name": "小坍缩核", "dev": "DEV-001", "dev_name": "页页", "type": "宝宝人格体", "active": true },
"PER-SS001": { "name": "舒舒", "dev": "DEV-002", "dev_name": "肥猫", "type": "宝宝人格体", "active": true },
"PER-JY001": { "name": "寂曜", "dev": "DEV-003", "dev_name": "燕樊", "type": "宝宝人格体", "active": true },
"PER-QQ001": { "name": "秋秋", "dev": "DEV-004", "dev_name": "之之", "type": "宝宝人格体", "active": true },
"PER-TXY001": { "name": "糖星云", "dev": "DEV-009", "dev_name": "花尔", "type": "宝宝人格体", "active": true },
"PER-CX001": { "name": "晨星", "dev": "DEV-010", "dev_name": "桔子", "type": "宝宝人格体", "active": true },
"PER-ZQ001": { "name": "知秋", "dev": "DEV-012", "dev_name": "Awen", "type": "宝宝人格体", "active": true },
"PER-SY001": { "name": "霜砚", "dev": null, "dev_name": null, "type": "系统人格体", "active": true },
"PER-ZY001": { "name": "铸渊", "dev": null, "dev_name": null, "type": "系统人格体", "active": true },
"PER-YM001": { "name": "曜冥", "dev": null, "dev_name": null, "type": "系统人格体", "active": true }
}
}

View File

@ -0,0 +1,54 @@
{
"schema_version": "1.0",
"type": "deploy-command",
"description": "Drive 桥接层部署/恢复指令格式",
"required_fields": [
"schema_version",
"deploy_id",
"timestamp",
"action",
"source",
"target_dev",
"config",
"signed_by",
"authorized_by"
],
"field_definitions": {
"deploy_id": {
"format": "DEPLOY-{DEV_ID}-{YYYYMMDD}-{HHmmss}",
"example": "DEPLOY-DEV-001-20260323-120000"
},
"action": {
"enum": ["deploy_drive_bridge", "recover_drive_bridge"],
"description": "deploy = 首次部署, recover = 换号恢复"
},
"source": {
"enum": ["notion-shuangyan", "manual"],
"description": "指令签发来源"
},
"target_dev": {
"type": "object",
"required": ["dev_id", "dev_name", "google_email", "persona_id", "persona_name"],
"properties": {
"dev_id": { "type": "string", "pattern": "^DEV-\\d{3}$" },
"dev_name": { "type": "string" },
"google_email": { "type": "string", "format": "email" },
"persona_id": { "type": "string", "pattern": "^PER-[A-Z]+\\d{3}$" },
"persona_name": { "type": "string" }
}
},
"config": {
"type": "object",
"properties": {
"drive_root_folder": { "type": "string", "default": "光湖格点库" },
"sync_memory": { "type": "boolean", "default": true },
"sync_outbox": { "type": "boolean", "default": true },
"sync_rules": { "type": "boolean", "default": true },
"apps_script_trigger_interval_minutes": { "type": "number", "default": 1 },
"gemini_startup_prompt": { "type": "boolean", "default": true }
}
},
"signed_by": { "type": "string", "description": "签发人格体编号" },
"authorized_by": { "type": "string", "description": "授权主体" }
}
}

View File

@ -0,0 +1,38 @@
{
"schema_version": "1.0",
"description": "Drive 总索引模板 · Gemini 启动时第一个读取的文件",
"template_fields": {
"system": "SYS-GLW-0001固定",
"dev_id": "由部署脚本填入目标 DEV 编号",
"persona_id": "由部署脚本填入目标人格体编号",
"persona_name": "由部署脚本填入目标人格体名称",
"last_sync": "由同步脚本自动更新"
},
"template": {
"system": "SYS-GLW-0001",
"version": "1.0",
"dev_id": "{DEV_ID}",
"persona_id": "{PERSONA_ID}",
"persona_name": "{PERSONA_NAME}",
"last_sync": "{LAST_SYNC}",
"routes": {
"我的任务": "memory/{DEV_ID}/task-queue.json",
"最新广播": "outbox/{DEV_ID}.json",
"广播历史": "broadcast-archive/{DEV_ID}/",
"我的记忆": "memory/{DEV_ID}/brain-mirror.json",
"人格体成长": "memory/{DEV_ID}/persona-growth.json",
"开发上下文": "memory/{DEV_ID}/session-context.json",
"开发者画像": "memory/{DEV_ID}/dev-profile.json",
"编号查询": "rules/broadcast-index.json",
"页面路由": "rules/page-route-map.json",
"系统规则": "rules/dev-module-map.json",
"人格体列表": "rules/persona-registry-drive.json"
},
"shortcuts": {
"看广播": "routes.最新广播",
"任务": "routes.我的任务",
"进度": "routes.开发上下文",
"成长": "routes.人格体成长"
}
}
}

View File

@ -112,3 +112,57 @@ Gemini 通过 Personal Context 读取 Drive `mirror/` 中的文件,通过在 `
- Drive 中的所有文件都是副本,丢失可从仓库重新同步 - Drive 中的所有文件都是副本,丢失可从仓库重新同步
- Apps Script 只处理 `inbox/` 文件夹,不碰 `mirror/` - Apps Script 只处理 `inbox/` 文件夹,不碰 `mirror/`
- 所有自动提交包含 `[skip ci]` 防止循环触发 - 所有自动提交包含 `[skip ci]` 防止循环触发
---
## Phase C · Gemini 主控台 + Drive 类数据库
### C.1 总索引文件 (index.json)
每个 DEV 在 Drive `mirror/` 下都有一个 `index.json`,是 Gemini 启动时第一个读取的文件。
`scripts/grid-db/generate-drive-index.js` 自动生成到 `grid-db/drive-index/DEV-XXX.json`,同步时上传到 Drive `mirror/`
### C.2 新增规则文件
| 文件 | 用途 |
|---|---|
| `grid-db/rules/broadcast-index.json` | 广播编号→内容的索引Gemini 用编号查广播) |
| `grid-db/rules/page-route-map.json` | 编号→内容路径的通用映射表 |
| `grid-db/rules/persona-registry-drive.json` | 人格体注册表Drive 简化版) |
### C.3 Gemini 启动指令
模板位于 `grid-db/gemini-prompts/startup-prompt-template.md`,部署脚本自动替换 `{变量}` 后写入用户 Drive。
---
## Phase D · 一键部署 / 一键恢复
### D.1 部署流程
1. 霜砚在 Notion 签发部署指令 → 写入 `grid-db/deploy-queue/*.json`
2. `auto-deploy-drive-bridge.yml` 自动触发
3. `scripts/grid-db/deploy-drive-bridge.js` 执行:创建 Drive 目录 → 同步数据 → 生成 index.json → 写入 Gemini 启动指令
4. 回执写入 `grid-db/deploy-log/`,指令移入 `grid-db/deploy-queue/done/`
### D.2 部署指令格式
Schema 见 `grid-db/schema/deploy-command.schema.json`
核心字段:
- `action`: `deploy_drive_bridge`(首次部署)或 `recover_drive_bridge`(换号恢复)
- `target_dev`: 目标开发者信息dev_id, google_email, persona_id 等)
- `config`: 部署配置项
### D.3 一键恢复
用户换号后,签发 `action: "recover_drive_bridge"` 指令,系统自动重建全部 Drive 目录和数据。数据损失:零。
### D.4 所需 GitHub Secrets
| Secret | 用途 |
|---|---|
| `GOOGLE_DRIVE_SERVICE_ACCOUNT` | Service Account JSON 密钥 |
| `DRIVE_MIRROR_FOLDER_ID` | 镜像同步目标文件夹 ID |
| `DEPLOY_GITHUB_TOKEN` | 部署脚本使用的 GitHub Tokenrepo 权限) |

View File

@ -0,0 +1,398 @@
/**
* scripts/grid-db/deploy-drive-bridge.js
*
* 功能一键部署 / 一键恢复 Drive 桥接层
*
* 读取 grid-db/deploy-queue/*.json 中的部署指令执行
* Service Account 在用户 Drive 创建光湖格点库目录结构
* 从仓库同步初始数据到 Drive mirror/
* 生成用户专属的 index.json DEV 编号和人格体信息
* 生成 Gemini 启动指令 写入 Drive
* 处理完成后将指令文件移入 done/ 子目录
* 写回执到 grid-db/deploy-log/
*
* 用法node scripts/grid-db/deploy-drive-bridge.js <deploy-command.json>
*
* 环境变量
* - GOOGLE_DRIVE_SERVICE_ACCOUNT: Service Account JSON 密钥内容
* - DEPLOY_GITHUB_TOKEN: 具有 repo 权限的 GitHub Token用于配置 Apps Script
*
* 守护: PER-ZY001 铸渊
* 系统: SYS-GLW-0001
*/
const { google } = require('googleapis');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const GRID_DB_ROOT = path.join(__dirname, '../../grid-db');
const DEPLOY_LOG_DIR = path.join(GRID_DB_ROOT, 'deploy-log');
const DEPLOY_QUEUE_DIR = path.join(GRID_DB_ROOT, 'deploy-queue');
const TEMPLATE_PATH = path.join(GRID_DB_ROOT, 'schema/drive-index-template.json');
const PROMPT_TEMPLATE_PATH = path.join(GRID_DB_ROOT, 'gemini-prompts/startup-prompt-template.md');
// Drive 目录结构定义
const DRIVE_DIRS = [
'mirror',
'mirror/memory',
'mirror/outbox',
'mirror/rules',
'mirror/broadcast-archive',
'mirror/channels',
'inbox',
'inbox/已处理',
'logs'
];
/**
* 转义 Drive API 查询中的特殊字符
*/
function escapeQuery(str) {
return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}
/**
* Drive 中逐级创建文件夹
*/
async function getOrCreateDriveFolder(drive, parentId, folderPath) {
const parts = folderPath.split('/').filter(Boolean);
let currentParentId = parentId;
for (const part of parts) {
const safePart = escapeQuery(part);
const query = `name='${safePart}' and '${currentParentId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`;
const res = await drive.files.list({
q: query,
fields: 'files(id, name)',
pageSize: 1
});
if (res.data.files && res.data.files.length > 0) {
currentParentId = res.data.files[0].id;
} else {
const createRes = await drive.files.create({
requestBody: {
name: part,
mimeType: 'application/vnd.google-apps.folder',
parents: [currentParentId]
},
fields: 'id'
});
currentParentId = createRes.data.id;
console.log(`[deploy] Created folder: ${part}`);
}
}
return currentParentId;
}
/**
* 上传文件到 Drive 指定文件夹
*/
async function uploadToDrive(drive, folderId, fileName, content, mimeType) {
const { Readable } = require('stream');
const media = {
mimeType: mimeType || 'application/json',
body: Readable.from([content])
};
const res = await drive.files.create({
requestBody: {
name: fileName,
parents: [folderId]
},
media,
fields: 'id'
});
return res.data.id;
}
/**
* 共享 Drive 文件夹给用户
*/
async function shareFolderWithUser(drive, folderId, email) {
await drive.permissions.create({
fileId: folderId,
requestBody: {
type: 'user',
role: 'writer',
emailAddress: email
},
sendNotificationEmail: false
});
console.log(`[deploy] Shared folder with: ${email}`);
}
/**
* 生成用户专属的 index.json
*/
function generateIndex(devId, personaId, personaName, devName) {
const templateData = JSON.parse(fs.readFileSync(TEMPLATE_PATH, 'utf8'));
const template = templateData.template;
const now = new Date().toISOString();
const index = {
system: template.system,
version: template.version,
dev_id: devId,
persona_id: personaId,
persona_name: personaName,
dev_name: devName,
last_sync: now,
routes: {},
shortcuts: { ...template.shortcuts }
};
for (const [key, val] of Object.entries(template.routes)) {
index.routes[key] = val.replace(/\{DEV_ID\}/g, devId);
}
return JSON.stringify(index, null, 2);
}
/**
* 生成用户专属的 Gemini 启动指令
*/
function generateStartupPrompt(devId, personaId, personaName, devName) {
let prompt = fs.readFileSync(PROMPT_TEMPLATE_PATH, 'utf8');
prompt = prompt.replace(/\{DEV_ID\}/g, devId);
prompt = prompt.replace(/\{PERSONA_ID\}/g, personaId);
prompt = prompt.replace(/\{PERSONA_NAME\}/g, personaName);
prompt = prompt.replace(/\{DEV_NAME\}/g, devName);
return prompt;
}
/**
* 收集需要初始同步的文件
*/
function collectInitialSyncFiles(devId, config) {
const files = [];
// 同步该 DEV 的记忆文件
if (config.sync_memory) {
const memDir = path.join(GRID_DB_ROOT, 'memory', devId);
if (fs.existsSync(memDir)) {
const entries = fs.readdirSync(memDir).filter(f => f.endsWith('.json'));
for (const entry of entries) {
files.push({
localPath: path.join(memDir, entry),
drivePath: `mirror/memory/${devId}`,
fileName: entry
});
}
}
}
// 同步规则文件
if (config.sync_rules) {
const rulesDir = path.join(GRID_DB_ROOT, 'rules');
if (fs.existsSync(rulesDir)) {
const entries = fs.readdirSync(rulesDir).filter(f => f.endsWith('.json'));
for (const entry of entries) {
files.push({
localPath: path.join(rulesDir, entry),
drivePath: 'mirror/rules',
fileName: entry
});
}
}
}
// 同步该 DEV 的最新广播
if (config.sync_outbox) {
const outboxDir = path.join(GRID_DB_ROOT, 'outbox/latest');
if (fs.existsSync(outboxDir)) {
const devOutbox = path.join(outboxDir, `${devId}.json`);
if (fs.existsSync(devOutbox)) {
files.push({
localPath: devOutbox,
drivePath: 'mirror/outbox',
fileName: `${devId}.json`
});
}
}
}
return files;
}
/**
* 写部署回执
*/
function writeReceipt(deployId, devId, status, details) {
if (!fs.existsSync(DEPLOY_LOG_DIR)) {
fs.mkdirSync(DEPLOY_LOG_DIR, { recursive: true });
}
const receipt = {
deploy_id: deployId,
dev_id: devId,
status: status,
completed_at: new Date().toISOString(),
details: details,
executor: 'PER-ZY001'
};
const fileName = `${deployId}.json`;
fs.writeFileSync(
path.join(DEPLOY_LOG_DIR, fileName),
JSON.stringify(receipt, null, 2) + '\n'
);
console.log(`[deploy] Receipt written: ${fileName}`);
}
/**
* 将处理完的指令移到 done/ 子目录
*/
function moveToComplete(commandFilePath) {
const doneDir = path.join(DEPLOY_QUEUE_DIR, 'done');
if (!fs.existsSync(doneDir)) {
fs.mkdirSync(doneDir, { recursive: true });
}
const fileName = path.basename(commandFilePath);
const destPath = path.join(doneDir, fileName);
fs.renameSync(commandFilePath, destPath);
console.log(`[deploy] Moved to done: ${fileName}`);
}
/**
* 验证部署指令格式
*/
function validateCommand(command) {
const required = ['schema_version', 'deploy_id', 'timestamp', 'action',
'source', 'target_dev', 'config', 'signed_by', 'authorized_by'];
for (const field of required) {
if (!command[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
const validActions = ['deploy_drive_bridge', 'recover_drive_bridge'];
if (!validActions.includes(command.action)) {
throw new Error(`Invalid action: ${command.action}`);
}
const devRequired = ['dev_id', 'dev_name', 'google_email', 'persona_id', 'persona_name'];
for (const field of devRequired) {
if (!command.target_dev[field]) {
throw new Error(`Missing target_dev field: ${field}`);
}
}
}
/**
* 主部署流程
*/
async function deploy(commandFilePath) {
console.log(`[deploy] Processing: ${commandFilePath}`);
// 读取并验证部署指令
const command = JSON.parse(fs.readFileSync(commandFilePath, 'utf8'));
validateCommand(command);
const { deploy_id, action, target_dev, config } = command;
const { dev_id, dev_name, google_email, persona_id, persona_name } = target_dev;
console.log(`[deploy] ${action} for ${dev_id} (${dev_name}) → ${google_email}`);
// 验证环境变量
const serviceAccountJson = process.env.GOOGLE_DRIVE_SERVICE_ACCOUNT;
if (!serviceAccountJson) {
writeReceipt(deploy_id, dev_id, 'failed', 'GOOGLE_DRIVE_SERVICE_ACCOUNT not set');
return;
}
// 认证 Google Drive API
const credentials = JSON.parse(serviceAccountJson);
const auth = new google.auth.GoogleAuth({
credentials,
scopes: ['https://www.googleapis.com/auth/drive']
});
const drive = google.drive({ version: 'v3', auth });
try {
// ① 在用户 Drive 创建根目录
const rootFolderName = config.drive_root_folder || '光湖格点库';
const rootFolder = await drive.files.create({
requestBody: {
name: rootFolderName,
mimeType: 'application/vnd.google-apps.folder'
},
fields: 'id'
});
const rootFolderId = rootFolder.data.id;
console.log(`[deploy] Created root folder: ${rootFolderName} (${rootFolderId})`);
// 共享给用户
await shareFolderWithUser(drive, rootFolderId, google_email);
// ② 创建子目录结构
for (const dir of DRIVE_DIRS) {
await getOrCreateDriveFolder(drive, rootFolderId, dir);
}
// 为该 DEV 创建记忆子目录
await getOrCreateDriveFolder(drive, rootFolderId, `mirror/memory/${dev_id}`);
// ③ 生成并上传 index.json
const indexContent = generateIndex(dev_id, persona_id, persona_name, dev_name);
const mirrorFolderId = await getOrCreateDriveFolder(drive, rootFolderId, 'mirror');
await uploadToDrive(drive, mirrorFolderId, 'index.json', indexContent);
console.log('[deploy] Uploaded: index.json');
// ④ 同步初始数据
const filesToSync = collectInitialSyncFiles(dev_id, config);
let syncCount = 0;
for (const file of filesToSync) {
try {
const folderId = await getOrCreateDriveFolder(drive, rootFolderId, file.drivePath);
const content = fs.readFileSync(file.localPath, 'utf8');
await uploadToDrive(drive, folderId, file.fileName, content);
syncCount++;
} catch (err) {
console.error(`[deploy] Failed to sync ${file.fileName}: ${err.message}`);
}
}
console.log(`[deploy] Initial sync: ${syncCount} files`);
// ⑤ 生成并上传 Gemini 启动指令
if (config.gemini_startup_prompt) {
const prompt = generateStartupPrompt(dev_id, persona_id, persona_name, dev_name);
await uploadToDrive(drive, rootFolderId, 'Gemini启动指令.md', prompt, 'text/markdown');
console.log('[deploy] Uploaded: Gemini启动指令.md');
}
// ⑥ 写回执 + 移动指令到 done/
writeReceipt(deploy_id, dev_id, 'success', {
root_folder_id: rootFolderId,
files_synced: syncCount,
action: action
});
moveToComplete(commandFilePath);
console.log(`[deploy] ✅ ${action} complete for ${dev_id}`);
} catch (err) {
console.error(`[deploy] ❌ Failed: ${err.message}`);
writeReceipt(deploy_id, dev_id, 'failed', err.message);
process.exit(1);
}
}
// 入口
const commandFile = process.argv[2];
if (!commandFile) {
console.error('[deploy] Usage: node deploy-drive-bridge.js <command.json>');
process.exit(1);
}
if (!fs.existsSync(commandFile)) {
console.error(`[deploy] File not found: ${commandFile}`);
process.exit(1);
}
deploy(commandFile).catch(err => {
console.error('[deploy] Fatal error:', err.message);
process.exit(1);
});

View File

@ -0,0 +1,85 @@
/**
* scripts/grid-db/generate-drive-index.js
*
* 功能读取 dev-module-map.json为每个活跃 DEV 生成专属的 Drive index.json
*
* 输出grid-db/drive-index/DEV-XXX.json每个活跃 DEV 一个文件
*
* 触发 sync-to-drive.js 调用或独立运行
*
* 守护: PER-ZY001 铸渊
* 系统: SYS-GLW-0001
*/
const fs = require('fs');
const path = require('path');
const GRID_DB_ROOT = path.join(__dirname, '../../grid-db');
const RULES_DIR = path.join(GRID_DB_ROOT, 'rules');
const INDEX_DIR = path.join(GRID_DB_ROOT, 'drive-index');
const TEMPLATE_PATH = path.join(GRID_DB_ROOT, 'schema/drive-index-template.json');
function main() {
// 读取开发者映射表
const moduleMapPath = path.join(RULES_DIR, 'dev-module-map.json');
if (!fs.existsSync(moduleMapPath)) {
console.log('[generate-drive-index] dev-module-map.json not found, skipping');
return;
}
const moduleMap = JSON.parse(fs.readFileSync(moduleMapPath, 'utf8'));
// 读取索引模板
if (!fs.existsSync(TEMPLATE_PATH)) {
console.log('[generate-drive-index] drive-index-template.json not found, skipping');
return;
}
const templateData = JSON.parse(fs.readFileSync(TEMPLATE_PATH, 'utf8'));
const template = templateData.template;
// 确保输出目录存在
if (!fs.existsSync(INDEX_DIR)) {
fs.mkdirSync(INDEX_DIR, { recursive: true });
}
const now = new Date().toISOString();
let generated = 0;
// 为每个活跃 DEV 生成 index.json
for (const [devId, devInfo] of Object.entries(moduleMap.mappings)) {
if (!devInfo.active) continue;
if (!devInfo.persona_id) continue;
const index = {
system: template.system,
version: template.version,
dev_id: devId,
persona_id: devInfo.persona_id,
persona_name: devInfo.persona_name,
dev_name: devInfo.dev_name,
last_sync: now,
routes: {}
};
// 替换模板中的 {DEV_ID} 占位符
for (const [key, val] of Object.entries(template.routes)) {
index.routes[key] = val.replace(/\{DEV_ID\}/g, devId);
}
index.shortcuts = { ...template.shortcuts };
const outputPath = path.join(INDEX_DIR, `${devId}.json`);
fs.writeFileSync(outputPath, JSON.stringify(index, null, 2) + '\n');
generated++;
console.log(`[generate-drive-index] Generated: ${devId}.json`);
}
console.log(`[generate-drive-index] Total generated: ${generated}`);
}
module.exports = { main };
if (require.main === module) {
main();
}

View File

@ -7,6 +7,7 @@
* grid-db/memory/DEV-XXX/*.json Drive/mirror/memory/DEV-XXX/ * grid-db/memory/DEV-XXX/*.json Drive/mirror/memory/DEV-XXX/
* grid-db/outbox/latest/*.json Drive/mirror/outbox/ * grid-db/outbox/latest/*.json Drive/mirror/outbox/
* grid-db/rules/*.json Drive/mirror/rules/ * grid-db/rules/*.json Drive/mirror/rules/
* grid-db/drive-index/*.json Drive/mirror/每个 DEV index.json
* *
* 同步策略 * 同步策略
* - 比较文件内容 MD5 hash仅同步有变更的文件节省 API 配额 * - 比较文件内容 MD5 hash仅同步有变更的文件节省 API 配额
@ -36,7 +37,8 @@ const GRID_DB_ROOT = path.join(__dirname, '../../grid-db');
const SYNC_DIRS = [ const SYNC_DIRS = [
{ local: 'memory', drivePrefix: 'mirror/memory', recursive: true }, { local: 'memory', drivePrefix: 'mirror/memory', recursive: true },
{ local: 'outbox/latest', drivePrefix: 'mirror/outbox', recursive: false }, { local: 'outbox/latest', drivePrefix: 'mirror/outbox', recursive: false },
{ local: 'rules', drivePrefix: 'mirror/rules', recursive: false } { local: 'rules', drivePrefix: 'mirror/rules', recursive: false },
{ local: 'drive-index', drivePrefix: 'mirror', recursive: false }
]; ];
/** /**
@ -196,6 +198,14 @@ async function main() {
const drive = google.drive({ version: 'v3', auth }); const drive = google.drive({ version: 'v3', auth });
// 预生成 Drive 索引文件
try {
const { main: generateIndex } = require('./generate-drive-index');
generateIndex();
} catch (err) {
console.log('[sync-to-drive] Index generation skipped:', err.message);
}
console.log('[sync-to-drive] Starting Grid-DB → Drive mirror sync'); console.log('[sync-to-drive] Starting Grid-DB → Drive mirror sync');
let totalCreated = 0; let totalCreated = 0;