Add smart ACK provider for WeChat MP replies
Replace fixed ackText with a rule-based AckProvider that picks response templates by message type and intent (translate, summary, rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync, zero I/O, auto-falls back to config.ackText on any error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
# 103 / 105 一次性升级实施记录(2026-06-26)
|
||||
|
||||
## 目标
|
||||
|
||||
本次升级的目标不是继续修补旧工作树,而是一次性把:
|
||||
|
||||
1. 本地三仓库收口为唯一源码真相。
|
||||
2. `103` 收口为“只接收发布包”的正式运行机。
|
||||
3. `105` 收口为入口 / 代理机,不再承担可写业务数据目录。
|
||||
|
||||
## 已确认的真实结构
|
||||
|
||||
### 103
|
||||
|
||||
- 主机:`john@58.38.22.103`
|
||||
- 主运行根目录:`/Users/john/Project`
|
||||
- 当前业务目录:
|
||||
- `/Users/john/Project/Memind`
|
||||
- `/Users/john/Project/memind_adm`
|
||||
- `/Users/john/Project/memind_plaza`
|
||||
- 当前产物目录骨架已经存在:
|
||||
- `/Users/john/Project/backups`
|
||||
- `/Users/john/Project/incoming`
|
||||
- `/Users/john/Project/releases`
|
||||
|
||||
### 105
|
||||
|
||||
- 可达 SSH 入口:`root@120.26.184.105`
|
||||
- 旧文档中的固定内网地址 `100.101.255.32` 当前已视为废弃,不应继续写入脚本或说明
|
||||
- 当前 H5 运行目录:`/root/tkmind_go/ui/h5`
|
||||
- 当前 Plaza 目录:`/root/tkmind_go/ui/plaza`
|
||||
- 105 上没有 Goose 双实例,只是入口 / portal 层
|
||||
|
||||
## 关键结论
|
||||
|
||||
### 1. MindSpace 数据真身在 103,不在 105
|
||||
|
||||
103 `.env` 明确指向:
|
||||
|
||||
- `H5_USERS_ROOT=/Users/john/Project/Memind/users`
|
||||
- `MINDSPACE_STORAGE_ROOT=/Users/john/Project/Memind/data/mindspace`
|
||||
- `MEMIND_SHARED_PUBLISH_ROOT=/Users/john/Project/Memind/MindSpace`
|
||||
|
||||
因此:
|
||||
|
||||
- `103` 是 MindSpace 用户空间、发布页、资产存储的真实落盘位置
|
||||
- 105 不是这批数据的主存储
|
||||
|
||||
### 2. 105 通过 rclone 挂载 103 的共享目录
|
||||
|
||||
105 现有 systemd mount:
|
||||
|
||||
- `memind-shared-mindspace.mount.service`
|
||||
- 把 `memind-mac:/Users/john/Project/Memind/MindSpace` 挂到 `/mnt/memind-shared/MindSpace`
|
||||
- `memind-shared-storage.mount.service`
|
||||
- 把 `memind-mac:/Users/john/Project/Memind/data/mindspace` 挂到 `/mnt/memind-shared/data/mindspace`
|
||||
|
||||
这意味着:
|
||||
|
||||
- 105 的无缝升级前提是 **103 上这两条真实路径不能变**
|
||||
- 可以重建代码目录
|
||||
- 不能随意移动这两棵持久目录的位置
|
||||
|
||||
### 3. 本次升级不能把 MindSpace 数据从路径上“迁走”
|
||||
|
||||
如果本次升级把以下路径改掉:
|
||||
|
||||
- `/Users/john/Project/Memind/MindSpace`
|
||||
- `/Users/john/Project/Memind/data/mindspace`
|
||||
- `/Users/john/Project/Memind/users`
|
||||
|
||||
那么:
|
||||
|
||||
- 103 本地服务会断引用
|
||||
- 105 的 rclone mount 会失效
|
||||
- 旧发布页 / 用户空间会出现访问中断
|
||||
|
||||
因此本次升级的正确策略是:
|
||||
|
||||
1. 保持这三条路径不变
|
||||
2. 让新 release 继续继承这些目录
|
||||
3. 把“源码目录可手改”废除,而不是把“数据根路径”迁走
|
||||
|
||||
## 本次备份结果
|
||||
|
||||
### 103
|
||||
|
||||
备份目录:
|
||||
|
||||
- `/Users/john/Project/backups/upgrade-20260626-094108`
|
||||
|
||||
包含:
|
||||
|
||||
- `103-Memind-live.tgz`
|
||||
- `103-Memind-persistent.tgz`
|
||||
- `103-memind_adm-live.tgz`
|
||||
- `103-memind_plaza-live.tgz`
|
||||
|
||||
其中最关键的是:
|
||||
|
||||
- `103-Memind-persistent.tgz`
|
||||
|
||||
它单独保存了:
|
||||
|
||||
- `MindSpace`
|
||||
- `data`
|
||||
- `users`
|
||||
- `public/plaza-covers`
|
||||
- `.env`
|
||||
|
||||
### 105
|
||||
|
||||
备份目录:
|
||||
|
||||
- `/root/service_backup/tkmind-upgrade-20260626-094109`
|
||||
|
||||
包含:
|
||||
|
||||
- `105-h5-live.tgz`
|
||||
- `105-plaza-live.tgz`
|
||||
- `105-h5.env`
|
||||
- `105-service-config.tgz`
|
||||
|
||||
## 当前数据体量
|
||||
|
||||
### 103
|
||||
|
||||
- `/Users/john/Project/Memind/MindSpace`: `95M`
|
||||
- `/Users/john/Project/Memind/data`: `40M`
|
||||
- `/Users/john/Project/Memind/users`: `52K`
|
||||
- `/Users/john/Project/memind_adm`: `112M`
|
||||
- `/Users/john/Project/memind_plaza`: `617M`
|
||||
- `/Users/john/Project/tkmind_go`: `45G`
|
||||
|
||||
### 重要说明
|
||||
|
||||
`/Users/john/Project/tkmind_go` 很大,但它不是本次无缝迁移的关键业务数据真身。
|
||||
|
||||
本次升级不应先去搬这 `45G`,而应优先保证:
|
||||
|
||||
1. `Memind` 的持久目录不丢
|
||||
2. `105` 的入口配置不丢
|
||||
3. 三个本地仓库的首次正式 bundle 发布可回退
|
||||
|
||||
## 推荐升级顺序
|
||||
|
||||
### 第一步:保留 103 持久路径不动
|
||||
|
||||
以下路径本次不改名、不挪位置:
|
||||
|
||||
- `/Users/john/Project/Memind/MindSpace`
|
||||
- `/Users/john/Project/Memind/data/mindspace`
|
||||
- `/Users/john/Project/Memind/users`
|
||||
- `/Users/john/Project/Memind/public/plaza-covers`
|
||||
|
||||
### 第二步:三项目按发布包重建代码版本
|
||||
|
||||
建议顺序:
|
||||
|
||||
1. `Memind`
|
||||
2. `memind_adm`
|
||||
3. `memind_plaza`
|
||||
|
||||
原因:
|
||||
|
||||
1. `Memind` 决定主持久目录和 105 的共享挂载根
|
||||
2. `memind_adm` 独立但依赖 `Memind` 共享实现
|
||||
3. `memind_plaza` 最后切,避免前端入口先于主共享资源变化
|
||||
|
||||
### 第三步:105 只保留入口角色
|
||||
|
||||
本次升级后,105 应保留:
|
||||
|
||||
1. `goose-h5.service`
|
||||
2. 反代 / 入口相关配置
|
||||
3. 指向 103 的共享挂载与远端 API 目标
|
||||
|
||||
本次升级后,105 不应继续承担:
|
||||
|
||||
1. 可写业务数据真身
|
||||
2. 源码发布主线
|
||||
3. Goose 实例
|
||||
|
||||
## 这次如何做到“无缝”
|
||||
|
||||
无缝的关键不是不重启,而是:
|
||||
|
||||
1. 数据路径保持不变
|
||||
2. 新版本继承同一批持久目录
|
||||
3. 入口配置不突然指向新路径
|
||||
4. 每次切换都有可回退包
|
||||
|
||||
因此本次切换原则是:
|
||||
|
||||
1. 先切代码
|
||||
2. 不搬数据根
|
||||
3. 不改共享路径
|
||||
4. 切完再验证挂载和业务路径
|
||||
|
||||
## 切换后必须验证
|
||||
|
||||
### 103
|
||||
|
||||
1. `http://127.0.0.1:8081/api/status`
|
||||
2. `http://127.0.0.1:3001/plaza`
|
||||
3. MindSpace 已有用户空间可读
|
||||
4. 发布页可打开
|
||||
5. `data/mindspace/users/*` 能继续访问对应资产
|
||||
|
||||
### 105
|
||||
|
||||
1. `goose-h5.service` 正常
|
||||
2. `/mnt/memind-shared/MindSpace` 挂载正常
|
||||
3. `/mnt/memind-shared/data/mindspace` 挂载正常
|
||||
4. H5 请求仍能正确回源 103 的 Goose / shared data
|
||||
@@ -0,0 +1,244 @@
|
||||
# 103 与本地差异报告(2026-06-26)
|
||||
|
||||
## 目的
|
||||
|
||||
这份报告用于回答三个问题:
|
||||
|
||||
1. `103` 当前运行代码与本地代码差异到底在哪里。
|
||||
2. 哪些差异可以忽略,哪些必须回收到本地。
|
||||
3. 后续如何把生产发布收口到“只能本地打包发布”。
|
||||
|
||||
本次对比没有直接污染本地主开发目录,而是先从 `103` 拉出只读基线副本:
|
||||
|
||||
- `/Users/john/PycharmProjects/test/_103_baselines/Memind`
|
||||
- `/Users/john/PycharmProjects/test/_103_baselines/memind_adm`
|
||||
|
||||
## 总结结论
|
||||
|
||||
### Memind
|
||||
|
||||
`test-memind` 当前代码内容与 `103` 拉下来的只读基线已经非常接近,重点业务文件基本一致。
|
||||
|
||||
可见差异主要是:
|
||||
|
||||
- 生产侧多了 `.release-manifest.txt`
|
||||
- 本地多了一些文档、审计脚本和历史备份文件
|
||||
|
||||
结论:
|
||||
|
||||
- `Memind` 不存在明显的“线上独有核心逻辑未回收”问题
|
||||
- 后续只需要继续坚持发布包流程,不应再直接改 `103`
|
||||
|
||||
### memind_adm
|
||||
|
||||
`test-memindadm` 与 `103` 基线仍然存在一批结构性差异,暂时不能直接把“线上就是最新”或“本地可以无脑全量覆盖”当成事实。
|
||||
|
||||
这些差异集中在:
|
||||
|
||||
- 用户详情与空间额度字段
|
||||
- 订阅计划同步接口
|
||||
- LLM provider 加载方式
|
||||
- 启动注入与路由装配
|
||||
|
||||
结论:
|
||||
|
||||
- `memind_adm` 需要一次明确的差异回收
|
||||
- 但回收动作应该发生在本地代码库中
|
||||
- 生产只能作为对账依据,不能继续反向当开发主线
|
||||
|
||||
## 本次对比方法
|
||||
|
||||
### 只读基线
|
||||
|
||||
从 `103` 通过 SSH 22 拉取只读副本,并排除运行态内容:
|
||||
|
||||
- `.git`
|
||||
- `.env`
|
||||
- `node_modules`
|
||||
- `dist`
|
||||
- 日志
|
||||
- pid 文件
|
||||
- `.mindops`
|
||||
|
||||
这样做的目的是:
|
||||
|
||||
- 保留真实源码结构
|
||||
- 不把线上运行态垃圾带回本地
|
||||
- 不覆盖本地主开发工作区
|
||||
|
||||
### 对比范围
|
||||
|
||||
优先比对高价值文件:
|
||||
|
||||
- 服务启动入口
|
||||
- API 路由
|
||||
- 用户与计费相关页面
|
||||
- 类型定义
|
||||
- 发布脚本
|
||||
- 部署文档
|
||||
|
||||
## Memind 对账结果
|
||||
|
||||
### 生产独有
|
||||
|
||||
- `.release-manifest.txt`
|
||||
|
||||
这是正常的发布产物,不需要回收到源码仓库。
|
||||
|
||||
### 本地独有
|
||||
|
||||
- `docs/103-reconciliation-2026-06-26.md`
|
||||
- `scripts/audit-103-state.sh`
|
||||
- `scripts/g2-lb.Caddyfile.bak-20260616-204004`
|
||||
- `scripts/g2-lb.Caddyfile.bak-20260619-162631`
|
||||
- `server.mjs.bak-20260616-195243`
|
||||
- `.gitignore`
|
||||
|
||||
这些内容里:
|
||||
|
||||
- 文档与审计脚本应保留在本地仓库
|
||||
- 备份文件需要后续择机清理
|
||||
|
||||
### 重点文件比对
|
||||
|
||||
以下关键文件对比结果为一致:
|
||||
|
||||
- `server.mjs`
|
||||
- `user-auth.mjs`
|
||||
- `db.mjs`
|
||||
- `schema.sql`
|
||||
- `src/App.tsx`
|
||||
- `src/api/client.ts`
|
||||
- `src/components/MindSpaceView.tsx`
|
||||
- `package.json`
|
||||
- `rsync_to_server.sh`
|
||||
- `scripts/release-prod.sh`
|
||||
- `docs/release-deploy.md`
|
||||
|
||||
判断:
|
||||
|
||||
- `Memind` 当前无需做额外的线上热修回收
|
||||
- 可以直接进入“只允许发布包上线”的治理阶段
|
||||
|
||||
## memind_adm 对账结果
|
||||
|
||||
### 生产独有
|
||||
|
||||
- `server/llm-provider-loader.mjs`
|
||||
|
||||
这说明当前线上仍保留一层 provider 加载包装逻辑,而本地已改成直接走共享实现。
|
||||
|
||||
这不是简单的“多一个文件”,它意味着本地与线上在启动装配方式上已经分叉。
|
||||
|
||||
### 本地独有
|
||||
|
||||
- `PRODUCTION_RELEASE_RULES.md`
|
||||
- `scripts/.releaseignore-prod`
|
||||
- `scripts/release-prod.sh`
|
||||
- `server/plan-sync.mjs`
|
||||
|
||||
这里面:
|
||||
|
||||
- 发布规则与发布脚本属于这次治理新增,应保留
|
||||
- `server/plan-sync.mjs` 是本地新增业务能力,需要确认是否就是想带到生产的新主线
|
||||
|
||||
### 双方都改了的重点文件
|
||||
|
||||
- `.env.example`
|
||||
- `docs/DEPLOY.md`
|
||||
- `package.json`
|
||||
- `scripts/rsync_to_server.sh`
|
||||
- `server/app.mjs`
|
||||
- `server/bootstrap.mjs`
|
||||
- `server/index.mjs`
|
||||
- `src/admin/pages/BillingPage.tsx`
|
||||
- `src/admin/pages/UserDetailPage.tsx`
|
||||
- `src/admin/pages/UsersPage.tsx`
|
||||
- `src/api/client.ts`
|
||||
- `src/types.ts`
|
||||
|
||||
### 高价值差异解读
|
||||
|
||||
#### 1. 用户详情与空间额度
|
||||
|
||||
本地版本新增了:
|
||||
|
||||
- `GET /users/:userId`
|
||||
- `spaceQuotaBytes`
|
||||
- `spaceUsedBytes`
|
||||
- `spaceReservedBytes`
|
||||
- `spaceAvailableBytes`
|
||||
|
||||
同时前端用户详情页和用户列表页也接入了这些字段。
|
||||
|
||||
这组改动和“后台可调空间、前台可购买空间”的需求方向一致,应视为本地主线能力,而不是线上应保留的旧逻辑。
|
||||
|
||||
#### 2. 订阅计划同步
|
||||
|
||||
本地版本新增了计划同步服务与同步结果结构:
|
||||
|
||||
- `server/plan-sync.mjs`
|
||||
- `PlanSyncResult`
|
||||
- Billing 页面同步结果展示
|
||||
|
||||
这组改动属于本地主线增强能力,线上基线暂未完整具备。
|
||||
|
||||
#### 3. LLM provider 装配方式
|
||||
|
||||
线上基线:
|
||||
|
||||
- `server/bootstrap.mjs` 通过 `./llm-provider-loader.mjs` 加载
|
||||
|
||||
本地版本:
|
||||
|
||||
- 直接从共享实现创建 `createLlmProviderService`
|
||||
|
||||
这是当前最需要审慎处理的一组差异,因为它影响服务启动边界,而不是单纯 UI 逻辑。
|
||||
|
||||
#### 4. 启动与监听方式
|
||||
|
||||
本地 `server/index.mjs` 增加了:
|
||||
|
||||
- `createPlanSyncService`
|
||||
- 显式绑定 `127.0.0.1`
|
||||
|
||||
这类差异需要和 `103` 现有反向代理、双 Goose、启动脚本一起确认,但不需要为了本地开发去复制生产双负载拓扑。
|
||||
|
||||
## 风险判断
|
||||
|
||||
### 可以接受的不一致
|
||||
|
||||
- 生产 Goose 是双实例负载,本地不是
|
||||
- 生产目录里有发布清单和运行态资产,本地没有
|
||||
- 本地为了开发保留文档、审计脚本和发布脚本
|
||||
|
||||
这些不一致属于“环境差异”,不是“源码真相冲突”。
|
||||
|
||||
### 必须收口的不一致
|
||||
|
||||
- `memind_adm` 启动装配路径分叉
|
||||
- 用户详情与空间字段相关接口分叉
|
||||
- 订阅计划同步相关接口分叉
|
||||
- 生产工作树仍然允许历史上留下的手改漂移存在
|
||||
|
||||
这些不一致会直接影响后续版本归属,必须在本地仓库中收口。
|
||||
|
||||
## 推荐收口顺序
|
||||
|
||||
1. 以本地 `test-memindadm` 为主线,明确保留“空间额度 + 用户详情 + 订阅同步”这组新能力。
|
||||
2. 单独审查 `server/llm-provider-loader.mjs` 是否仍有线上必需逻辑。
|
||||
3. 如果该文件只是在做兼容装配,则把必需逻辑回收进本地主线,再删除这层分叉。
|
||||
4. 基于本地仓库走 `scripts/release-prod.sh` 做第一次正式发布。
|
||||
5. 发布成功后,把 `103` 只保留为运行目标与审计对象,不再作为源码修改点。
|
||||
|
||||
## 最终原则
|
||||
|
||||
`103` 是运行事实来源,但不是源码主线。
|
||||
|
||||
后续必须坚持:
|
||||
|
||||
1. 本地仓库合并差异
|
||||
2. 本地提交生成发布包
|
||||
3. 生产只接收发布包
|
||||
4. 不再直接 `rsync`
|
||||
5. 不再在线改源码
|
||||
@@ -0,0 +1,96 @@
|
||||
# Memind 103 收口方案(2026-06-26)
|
||||
|
||||
## 当前判断
|
||||
|
||||
`Memind` 已经接近“可以收口”的状态,但 `103` 当前运行目录已经不是一个可依赖 Git 的干净工作树。
|
||||
|
||||
已确认:
|
||||
|
||||
1. `103` 运行目录:`/Users/john/Project/Memind`
|
||||
2. `103` 当前目录存在 `.release-manifest.txt`,说明至少已有发布包切换痕迹。
|
||||
3. `103` 上当前 **无法直接把 `Memind` 当成 Git 工作树使用**。
|
||||
4. 本地 `test-memind` 是后续唯一应保留的源码主线。
|
||||
|
||||
## 这意味着什么
|
||||
|
||||
1. 以后不能再依赖“线上 `git rev-parse` 一致”来判断是否对齐。
|
||||
2. `103` 只能作为运行事实来源与只读基线来源。
|
||||
3. 真正要收口的动作,必须发生在本地仓库里。
|
||||
|
||||
## 当前差异判断
|
||||
|
||||
本地与 `103` 只读基线相比:
|
||||
|
||||
1. 业务主文件大体已经一致。
|
||||
2. 本地多出了规则文档、审计脚本、运行资源、历史备份文件。
|
||||
3. 这批差异里,大多数不是“线上独有必须回收”的功能逻辑。
|
||||
|
||||
## 收口目标
|
||||
|
||||
把 `Memind` 从“本地和线上都可能被手改”收口成:
|
||||
|
||||
1. 本地 `test-memind` 是唯一源码真相。
|
||||
2. `103` 只接收发布包,不再手改源码。
|
||||
3. 发布后通过 manifest、健康检查、业务验收确认版本一致。
|
||||
|
||||
## 具体执行步骤
|
||||
|
||||
### 第一步:冻结线上直改
|
||||
|
||||
1. 不再在 `/Users/john/Project/Memind` 直接改源码。
|
||||
2. 不再把 `103` 当作可直接修代码的工作目录。
|
||||
|
||||
### 第二步:只保留只读基线
|
||||
|
||||
当前只读基线目录:
|
||||
|
||||
- `/Users/john/PycharmProjects/test/_103_baselines/Memind`
|
||||
|
||||
用途:
|
||||
|
||||
1. 对账
|
||||
2. 取证
|
||||
3. 回收必要热修
|
||||
|
||||
禁止:
|
||||
|
||||
1. 用它覆盖本地主开发目录
|
||||
2. 把它误当主仓库继续开发
|
||||
|
||||
### 第三步:清理本地“非源码差异”
|
||||
|
||||
优先处理:
|
||||
|
||||
1. 历史 `.bak` 文件
|
||||
2. 明显的临时日志与运行输出
|
||||
3. 不应该长期存在于主仓库的临时资源
|
||||
|
||||
注意:
|
||||
|
||||
1. 先分类,再清理
|
||||
2. 不要误删正在使用的真实业务资源
|
||||
|
||||
### 第四步:做第一次收口发布
|
||||
|
||||
1. 本地把必须保留的改动整理成 commit。
|
||||
2. 用 `bash scripts/release-prod.sh` 生成发布包并发布到 `103`。
|
||||
3. 发布后保留 `.release-manifest.txt`、备份包路径、健康检查结果。
|
||||
|
||||
### 第五步:建立后续一致性
|
||||
|
||||
以后每次发布必须满足:
|
||||
|
||||
1. 本地先 commit
|
||||
2. 从 commit 打包
|
||||
3. `103` 只收包
|
||||
4. 发布后验证
|
||||
5. 出问题回滚
|
||||
|
||||
## 何时算收口完成
|
||||
|
||||
满足以下条件即可视为完成:
|
||||
|
||||
1. `103` 上不再直接改源码
|
||||
2. 本地变更都能追溯到 commit
|
||||
3. 最近一次线上版本来自 `scripts/release-prod.sh`
|
||||
4. 业务验收结果与 manifest 能对应到同一发布编号
|
||||
@@ -0,0 +1,138 @@
|
||||
# 103 生产收口记录(2026-06-26)
|
||||
|
||||
## 目标
|
||||
|
||||
把 `103` 从“可直接手改的运行工作树”收口为“只接收本地发布包的生产环境”。
|
||||
|
||||
这份记录只做两件事:
|
||||
|
||||
1. 固定 2026-06-26 看到的真实生产状态。
|
||||
2. 给后续“本地唯一发布源”改造提供基线。
|
||||
|
||||
## 已确认的生产真相
|
||||
|
||||
### 主机与入口
|
||||
|
||||
- 生产主机:`john@58.38.22.103`
|
||||
- 当前 SSH 端口:`22`
|
||||
- 旧的 `2222` 已关闭,不应再写入文档或脚本
|
||||
|
||||
### 运行目录
|
||||
|
||||
- Memind: `/Users/john/Project/Memind`
|
||||
- 管理后台: `/Users/john/Project/memind_adm`
|
||||
- Plaza: `/Users/john/Project/memind_plaza`
|
||||
|
||||
### 运行服务
|
||||
|
||||
- `cn.tkmind.memind-portal`
|
||||
- `cn.tkmind.plaza`
|
||||
- `cn.tkmind.memind-adm-web`
|
||||
- `cn.tkmind.memind-adm-api`
|
||||
- `cn.tkmind.goosed-18006`
|
||||
- `cn.tkmind.goosed-18007`
|
||||
|
||||
结论:Goose 在线上是双实例负载,本地不需要强行复制这套拓扑;这部分应作为“发布后巡检”处理,而不是“本地开发必须等价”处理。
|
||||
|
||||
## 数据库结论
|
||||
|
||||
### 生产正在使用的业务库
|
||||
|
||||
`/Users/john/Project/Memind/.env` 与 `/Users/john/Project/memind_adm/.env` 都指向同一个 RDS:
|
||||
|
||||
- `DATABASE_URL=.../goose`
|
||||
|
||||
因此:
|
||||
|
||||
- 当前正式业务库是 `goose`
|
||||
- `memind` 库仍存在,但不是当前正式流量的主写入目标
|
||||
|
||||
### 2026-06-26 抽样结果
|
||||
|
||||
`goose`:
|
||||
|
||||
- `h5_users`: `41`
|
||||
- 有充值/加款记录的用户数:`27`
|
||||
|
||||
`memind`:
|
||||
|
||||
- `h5_users`: `26`
|
||||
- 有充值/加款记录的用户数:`12`
|
||||
|
||||
这说明 `memind` 更像旧数据或历史分流,当前生产业务应以 `goose` 为准。
|
||||
|
||||
### 付费用户观察
|
||||
|
||||
按 `goose.h5_billing_ledger` 统计:
|
||||
|
||||
- 除去明显超大测试/管理员账户 `admin`
|
||||
- 充值额度最高的真实用户:`wx_95eskyda / cheng cong👑`
|
||||
- 实际消耗最高的真实用户之一:`wx_ul610et8 / 唐`
|
||||
|
||||
## 工作树漂移结论
|
||||
|
||||
### 103 生产工作树
|
||||
|
||||
- `Memind`
|
||||
- branch: `main`
|
||||
- commit: `728b01c`
|
||||
- dirty files: `154`
|
||||
|
||||
- `memind_adm`
|
||||
- branch: `main`
|
||||
- commit: `7a5b9cc`
|
||||
- dirty files: `42`
|
||||
|
||||
### 本地工作树
|
||||
|
||||
- `test-memind`
|
||||
- commit: `d51df2f`
|
||||
- local diff count: `102`
|
||||
|
||||
- `test-memindadm`
|
||||
- commit: `f217232`
|
||||
- local diff count: `28`
|
||||
|
||||
## 判断
|
||||
|
||||
不能把 `103` 视为“最新版本”,也不能把本地直接视为“可全量覆盖生产”的唯一事实。
|
||||
|
||||
当前状态更准确的描述是:
|
||||
|
||||
1. `103` 是真实运行版本,但带有大量未回收漂移。
|
||||
2. 本地是主要开发来源,但尚未完成对 `103` 有效差异的回收。
|
||||
3. 因此必须先做一次“收口”,再执行“本地唯一发布源”制度。
|
||||
|
||||
## 收口策略
|
||||
|
||||
### 禁止动作
|
||||
|
||||
- 禁止再用 `rsync` 直接覆盖生产工作树
|
||||
- 禁止在 `103` 直接改源码后继续跑
|
||||
- 禁止把 `103` 直接反向同步进本地主开发目录
|
||||
|
||||
### 允许动作
|
||||
|
||||
1. 单独创建 `103-baseline` 副本或 worktree
|
||||
2. 将 `103` 与本地、目标主线做三向比较
|
||||
3. 只回收“生产独有且必须保留”的热修
|
||||
4. 回收完成后,以本地提交为唯一发布源重新出包上线
|
||||
|
||||
## 后续制度
|
||||
|
||||
从这次收口完成后开始:
|
||||
|
||||
1. 本地源码是唯一发布源
|
||||
2. 生产只接收发布包
|
||||
3. 发布包上传到 `incoming/`
|
||||
4. 生产解包到 `releases/`
|
||||
5. 备份当前 live 目录
|
||||
6. 原子切换目录并重启
|
||||
7. 失败自动回滚
|
||||
|
||||
## 需要继续执行的事项
|
||||
|
||||
1. 为 `memind_adm` 补齐与 `Memind` 一样的发布包脚本和规则文档
|
||||
2. 从 `103` 导出一个只读差异清单
|
||||
3. 把真正必须保留的生产热修回收到本地
|
||||
4. 收口完成后,把 `103` 变成“不可手改源码”的发布目标
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
| H5 门户 | MindSpace、Plaza 发现广场、Agent Jobs |
|
||||
| 认证 | 微信登录绑定门控、PC 扫码、移动端引导 |
|
||||
| MindSpace | 页面实时编辑、Chat Skills、可视化 HTML 编辑器(预览内编辑 + undo/redo) |
|
||||
| Plaza | 105 入口反代到 Studio 的 `:3001` |
|
||||
| Plaza | 本机 Mac 为主服务,Cloudflare Tunnel 公网回源 |
|
||||
| 部署 | H5 / Plaza 本机与脚本化部署工具链 |
|
||||
|
||||
## 团队成员操作指南
|
||||
|
||||
+34
-29
@@ -1,49 +1,54 @@
|
||||
# g2.tkmind.cn 负载均衡(Studio + 105)
|
||||
|
||||
> 2026-06-17 上线。把 g2 H5 的请求按权重分到两台机器:Studio(主)和 105(灰度副)。
|
||||
> goose **只在 Studio 跑一份**,105 是无状态前端孪生,通过 Tailscale 把请求代理回 Studio 的 goosed。
|
||||
> Goose **只在 103 / Studio 跑**,105 是无状态前端孪生,通过固定公网地址把请求代理回 Studio 的 goosed。
|
||||
> 2026-06-22 已确认:Studio 上 Goose 不是单实例,而是双实例负载:`18006` 主、`18007` 备用/第二实例;105 仍然不跑 Goose。
|
||||
|
||||
## 流量拓扑
|
||||
|
||||
```
|
||||
用户 → 阿里云解析 → 105 服务器公网入口
|
||||
├─ 转发到本地 Mac 1.6 机器 → 本机 portal 127.0.0.1:8081
|
||||
├─ 转发到 103 / Studio 服务器 → Studio portal 127.0.0.1:8081
|
||||
└─ [SSH 正向隧道 127.0.0.1:18080] → 105 portal :8080
|
||||
│
|
||||
105 / 本地主机 两个 portal ── 都连 ──→ 本地 goosed 100.99.38.66:18006(唯一实例)
|
||||
发布页文件 ── 都读 ──→ 本地 canonical /Users/john/Project/Memind/MindSpace
|
||||
105 / Studio 两个 portal ── 都连 ──→ Studio goosed 58.38.22.103:18006(主)
|
||||
└────→ Studio goosed 58.38.22.103:18007(备用/第二实例)
|
||||
发布页文件 ── 都读 ──→ Studio canonical /Users/john/Project/Memind/MindSpace
|
||||
(105 经反向隧道 + rclone 挂载到本地同路径)
|
||||
```
|
||||
|
||||
- **为什么不用旧的 Cloudflare 直连**:当前入口已经迁到阿里云解析,外网流量先到 105,再由 105 转发回本地 Mac 1.6 机器。
|
||||
- **为什么 105 经 SSH 隧道接入**:入口机和 105 之间保持本地 HTTP/SSH 链路,避免把 105 公网地址直接写进上游。
|
||||
- **为什么不用旧的 Cloudflare 直连**:当前入口已经迁到阿里云解析,外网流量先到 105,再由 105 转发到 103 / Studio。
|
||||
- **为什么直接固定公网 IP**:当前服务器互联和部署链路统一使用固定公网地址 `120.26.184.105` / `58.38.22.103`,不再依赖内网地址、域名别名或临时回退链路。
|
||||
|
||||
## 调灰度比例(最常用)
|
||||
## 105 / 103 联通约束(硬性要求)
|
||||
|
||||
## 105 联通约束(硬性要求)
|
||||
**要求:域名只做用户入口;服务器互联、健康检查、部署同步统一走固定公网 IP。**
|
||||
|
||||
**要求:105 机器与本机通信必须走 Tailscale 隧道,不允许走外网 IP。**
|
||||
|
||||
- 访问、同步、部署 105 一律使用 `ssh105`。
|
||||
- `ssh105` 在 `~/.ssh/config` 中绑定为 `100.101.255.32` 并通过 `tailscale nc` 代理;
|
||||
- 现有脚本默认主机已改为 `root@ssh105`。
|
||||
- 本机对 105 的联通入口是 `127.0.0.1:18080`,不是 `105.tkmind.cn`。
|
||||
- 105 运维地址:`120.26.184.105`。
|
||||
- 103 / Studio 运维地址:`58.38.22.103`。
|
||||
- 如果 SSH 别名未更新,直接写 `root@120.26.184.105` / `john@58.38.22.103`。
|
||||
- Studio / goosed 目标直接写 `58.38.22.103`,不要写 `g2.tkmind.cn` 或 105 域名。
|
||||
- Studio 对 105 的联通入口是 `127.0.0.1:18080`,不是 `105.tkmind.cn`。
|
||||
|
||||
快速自检(每次操作 105 前):
|
||||
|
||||
```bash
|
||||
tailscale --socket /Users/john/Project/ollama/.tailscale/tailscaled.sock status
|
||||
tailscale --socket /Users/john/Project/ollama/.tailscale/tailscaled.sock ping 100.101.255.32
|
||||
ssh ssh105 'echo tunnel-ok-105'
|
||||
ping -c 1 120.26.184.105
|
||||
ping -c 1 58.38.22.103
|
||||
ssh root@120.26.184.105 'echo ssh-ok-105'
|
||||
ssh john@58.38.22.103 'echo ssh-ok-103'
|
||||
curl -s http://127.0.0.1:18080/api/status # 应返回 ok
|
||||
```
|
||||
|
||||
- 禁止:`HostName 120.26.184.105` 直接作为 105 脚本主机。
|
||||
- 禁止:用 `105.tkmind.cn` 做 API 健康检查或同步链路入口。
|
||||
- 禁止:把 105 公网 IP 当成业务上游写进 Caddy / goosed 目标;公网 IP 只保留给外部入口或应急 SSH。
|
||||
- 应急:如果 SSH 别名或本地缓存配置仍残留旧地址,显式设置 `H5_DEPLOY_HOST=root@120.26.184.105`,Studio 目标改为 `john@58.38.22.103`。
|
||||
|
||||
编辑 **本地 Mac 1.6** 上的入口配置,调整流量分配(第一个=本机 :8081,第二个=105 :18080):
|
||||
## 调灰度比例(最常用)
|
||||
|
||||
| 配置 | 本机 | 105 |
|
||||
编辑 **103 / Studio** 上的入口配置,调整流量分配(第一个=Studio :8081,第二个=105 :18080):
|
||||
|
||||
| 配置 | Studio | 105 |
|
||||
|------|------|-----|
|
||||
| `weighted_round_robin 19 1` | 95% | 5%(默认上线值) |
|
||||
| `weighted_round_robin 9 1` | 90% | 10% |
|
||||
@@ -53,7 +58,7 @@ curl -s http://127.0.0.1:18080/api/status # 应返回 ok
|
||||
改完**零停机生效**:
|
||||
|
||||
```bash
|
||||
ssh 本机入口机
|
||||
ssh john@58.38.22.103
|
||||
cd ~/Project/Memind/scripts
|
||||
caddy validate --config g2-lb.Caddyfile # 可选,先校验
|
||||
caddy reload --config g2-lb.Caddyfile # 零停机热重载
|
||||
@@ -74,7 +79,7 @@ caddy reload --config g2-lb.Caddyfile
|
||||
|
||||
## 验证 / 观测
|
||||
|
||||
每个 g2 响应都带 `X-Memind-Upstream` 头,标记实际命中的上游(`127.0.0.1:8081`=本机,`127.0.0.1:18080`=105):
|
||||
每个 g2 响应都带 `X-Memind-Upstream` 头,标记实际命中的上游(`127.0.0.1:8081`=Studio,`127.0.0.1:18080`=105):
|
||||
|
||||
```bash
|
||||
# 打 N 次看分流比例
|
||||
@@ -84,7 +89,7 @@ for i in $(seq 1 40); do
|
||||
done | sort | uniq -c
|
||||
|
||||
# 看入口反代两个上游健康状态
|
||||
curl -s http://127.0.0.1:2019/reverse_proxy/upstreams # 在本机入口机上执行
|
||||
curl -s http://127.0.0.1:2019/reverse_proxy/upstreams # 在 103 / Studio 上执行
|
||||
```
|
||||
|
||||
健康检查会每 10s 打一次各上游的 `/api/status`,连不上或非 200 就自动摘除该上游、流量全转到健康节点;恢复后自动加回。
|
||||
@@ -93,15 +98,15 @@ curl -s http://127.0.0.1:2019/reverse_proxy/upstreams # 在本机入口机上
|
||||
|
||||
| 组件 | 位置 | 作用 |
|
||||
|------|------|------|
|
||||
| 入口反代 | 本机入口机 `scripts/g2-lb.Caddyfile`(LaunchAgent `cn.tkmind.g2-lb`,:8090) | 入口转发 + 健康检查 |
|
||||
| 正向隧道 | 本机入口机 `scripts/memind-fwd-tunnel.sh`(LaunchAgent `cn.tkmind.memind-fwd-tunnel`) | 本机 `127.0.0.1:18080` → 105 `:8080` |
|
||||
| 反向隧道 | 本机入口机 `scripts/memind-mac-tunnel.sh`(LaunchAgent `cn.tkmind.memind-tunnel`) | 105 经此挂载本地 MindSpace 文件 |
|
||||
| 105 portal | 105 systemd `goose-h5`(:8080),`/root/tkmind_go/ui/h5/.env` | 无状态前端,`TKMIND_API_TARGET=https://100.99.38.66:18006` |
|
||||
| 入口反代 | 103 / Studio `scripts/g2-lb.Caddyfile`(LaunchAgent `cn.tkmind.g2-lb`,:8090) | 入口转发 + 健康检查 |
|
||||
| 正向隧道 | 103 / Studio `scripts/memind-fwd-tunnel.sh`(LaunchAgent `cn.tkmind.memind-fwd-tunnel`) | Studio `127.0.0.1:18080` → 105 `:8080` |
|
||||
| 反向隧道 | 103 / Studio `scripts/memind-mac-tunnel.sh`(LaunchAgent `cn.tkmind.memind-tunnel`) | 105 经此挂载 Studio MindSpace 文件 |
|
||||
| 105 portal | 105 systemd `goose-h5`(:8080),`/root/tkmind_go/ui/h5/.env` | 无状态前端,主 Goose 指向 `https://58.38.22.103:18006`,第二 Goose 指向 `https://58.38.22.103:18007` |
|
||||
| 105 文件挂载 | 105 systemd `.mount` → `/mnt/memind-shared`(rclone over 反向隧道) | 发布页/工作区共享只读 |
|
||||
|
||||
## 关键约束(改动前必读)
|
||||
|
||||
- **105 永不跑 goose**。goosed 单实例只在本地 Mac 1.6 机器;session 存本地 SQLite,文件操作也在本地完成。105 只是代理 + serve。
|
||||
- **105 永不跑 goose**。goosed 只在 103 / Studio;当前是双实例 `18006` / `18007`。105 只是代理 + serve。
|
||||
- 105 的 portal 必须设 `MEMIND_WORKSPACE_MAINTENANCE=0`、`MINDSPACE_AGENT_JOBS_ENABLED=false`——工作区维护守护(缩略图/资产同步 watcher)只该在本地跑,否则 105 对 rclone 挂载树做递归 fs.watch 会占满 libuv 线程池导致 portal 启动卡死。
|
||||
- 105 必须与本地主机跑**同一份代码**(用 `sync-to-105.sh` 从主机同步)和**相同的 `TKMIND_SERVER__SECRET_KEY`**(否则连不上 goosed / 解不开共享 RDS 里的加密设置)。
|
||||
- 105 必须与 103 / Studio 跑**同一份代码**(用 `sync-to-105.sh` 从 Studio 同步)和**相同的 `TKMIND_SERVER__SECRET_KEY`**(否则连不上 goosed / 解不开共享 RDS 里的加密设置)。
|
||||
- 调权重只动 `g2-lb.Caddyfile`,不要动 105 的 systemd 单元或 .env。
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
# gadm 由 105 入口转发到 Studio
|
||||
|
||||
`gadm.tkmind.cn` 的生产链路和 Plaza 一样,都是 `105 入口 -> Studio 生产机`。
|
||||
这里的后端服务名是 `memind_adm`,代码入口在本仓库的 `admin-server.mjs`。
|
||||
根路径 `/` 默认进入 `gadm` 的管理页,也就是 `ops` 前端里的 `/admin` 段。
|
||||
|
||||
## 角色拆分
|
||||
|
||||
```text
|
||||
浏览器 / 内部管理人员
|
||||
↓
|
||||
105 公网入口(gadm.tkmind.cn)
|
||||
↓ nginx / 反代
|
||||
Studio / 本机生产机
|
||||
├─ ops 前台(/ops/,Vite 开发服务)
|
||||
└─ memind_adm :8082(admin-server.mjs)
|
||||
├─ /healthz
|
||||
├─ /admin-api/*
|
||||
└─ /api/ops/v1/*
|
||||
```
|
||||
|
||||
`memind_adm` 只负责后端管理 API,不直接托管大部分前台页面。
|
||||
`ops/` 里的后台 SPA 是独立页面,生产上由 `gadm.tkmind.cn/` 先跳转到 `/ops/admin/`,再由 105 反代回 Studio。
|
||||
|
||||
## 本地开发
|
||||
|
||||
```bash
|
||||
pnpm dev:adm
|
||||
```
|
||||
|
||||
默认监听 `http://127.0.0.1:8082`。
|
||||
|
||||
常见本地访问地址:
|
||||
|
||||
| 服务 | 地址 |
|
||||
|------|------|
|
||||
| memind_adm | http://127.0.0.1:8082 |
|
||||
| ops 后台 | http://127.0.0.1:3002/ops/ |
|
||||
|
||||
## 测试机同步
|
||||
|
||||
如果要同步到测试目录,`memind_adm` 对应的是:
|
||||
|
||||
- 源码目录:`/Users/john/PycharmProjects/test/test-memind`
|
||||
- 测试镜像目录:`/Users/john/PycharmProjects/test/test-memindadm`
|
||||
|
||||
同步脚本:
|
||||
|
||||
```bash
|
||||
bash scripts/deploy-to-test-host.sh --only-adm
|
||||
```
|
||||
|
||||
如果你要同步三套目录,也可以直接:
|
||||
|
||||
```bash
|
||||
bash scripts/deploy-to-test-host.sh
|
||||
```
|
||||
|
||||
## 生产发布流程
|
||||
|
||||
生产上,`gadm.tkmind.cn` 解析到 105,105 负责把:
|
||||
|
||||
- `/` 重定向到 `/ops/admin/`
|
||||
- `/ops/` 反代到 Studio 上的 ops 页面
|
||||
- `/admin-api/` 和 `/api/ops/v1/` 反代到 Studio 上的 `memind_adm`
|
||||
|
||||
### 0. 本地改动确认
|
||||
|
||||
推荐从本地开发目录发布到 Studio:
|
||||
|
||||
```bash
|
||||
cd /Users/john/PycharmProjects/test/test-memind
|
||||
git status --short
|
||||
```
|
||||
|
||||
如果你改的是 `admin-server.mjs`、`admin-routes.mjs`、`admin-*.mjs` 或 `ops/`,记得先本地自测。
|
||||
|
||||
### 1. 本地启动 / 自测
|
||||
|
||||
```bash
|
||||
pnpm dev:adm
|
||||
```
|
||||
|
||||
默认监听 `http://127.0.0.1:8082`,启动后先看:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8082/healthz
|
||||
```
|
||||
|
||||
### 2. 同步到测试机
|
||||
|
||||
```bash
|
||||
bash scripts/deploy-to-test-host.sh --only-adm
|
||||
```
|
||||
|
||||
测试镜像目录是 `/Users/john/PycharmProjects/test/test-memindadm`。
|
||||
|
||||
### 3. Studio 生产机启动 / 重启
|
||||
|
||||
在 Studio 机器上,`memind_adm` 最终都应该由同一个入口启动:
|
||||
|
||||
```bash
|
||||
cd /Users/john/Project/Memind
|
||||
node admin-server.mjs
|
||||
```
|
||||
|
||||
如果你们在 Studio 上用了 launchd / pm2 / systemd,就让守护脚最终执行上面这条命令。
|
||||
|
||||
常见的生产检查:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8082/healthz
|
||||
curl -s http://127.0.0.1:8082/admin-api/summary
|
||||
```
|
||||
|
||||
### 4. 105 入口反代
|
||||
|
||||
105 上的 nginx 应该把 `gadm.tkmind.cn` 回源到 Studio 的 `memind_adm :8082`。
|
||||
|
||||
如果你要确认 nginx 当前配置,优先看:
|
||||
|
||||
```bash
|
||||
ssh ssh105 'nginx -t && systemctl reload nginx'
|
||||
ssh ssh105 'sed -n "1,220p" /etc/nginx/conf.d/gadm.tkmind.cn.conf'
|
||||
```
|
||||
|
||||
上线后优先验证:
|
||||
|
||||
```bash
|
||||
curl -I https://gadm.tkmind.cn/
|
||||
curl -I https://gadm.tkmind.cn/ops/admin/
|
||||
curl -s -D - -o /dev/null https://gadm.tkmind.cn/admin-api/summary
|
||||
curl -s https://gadm.tkmind.cn/healthz
|
||||
```
|
||||
|
||||
## 故障排查顺序
|
||||
|
||||
如果 `gadm.tkmind.cn` 挂了,按这个顺序查:
|
||||
|
||||
1. Studio 上 `http://127.0.0.1:8082/healthz` 是否正常
|
||||
2. Studio 上 `memind_adm` 是否还在运行
|
||||
3. 105 上 nginx 是否把 `gadm.tkmind.cn` 转发到了 Studio
|
||||
4. `ADMIN_CONSOLES`、`ADMIN_API_ALLOWED_HOSTS`、`OPS_API_ALLOWED_HOSTS` 是否限制过严
|
||||
5. `ops/` 是否还在用正确的 `'/admin-api'` 代理地址
|
||||
|
||||
## 相关文件
|
||||
|
||||
- [`admin-server.mjs`](/Users/john/PycharmProjects/test/test-memind/admin-server.mjs)
|
||||
- [`scripts/deploy-to-test-host.sh`](/Users/john/PycharmProjects/test/test-memind/scripts/deploy-to-test-host.sh)
|
||||
- [`scripts/local-test-proxy.mjs`](/Users/john/PycharmProjects/test/test-memind/scripts/local-test-proxy.mjs)
|
||||
+11
-18
@@ -1,40 +1,33 @@
|
||||
# 本地开发
|
||||
|
||||
> 生产安全提醒:`g2.tkmind.cn` 当前使用本机 `8081`。普通开发预览必须走测试端口,不要直接在生产目录运行默认 `pnpm dev`。完整规程见 [生产 / 测试 / 预览隔离规程](./service-isolation-runbook.md)。
|
||||
> 这是本机本地开发文档,只处理当前工作区里的源码联调,不做任何生产同步。
|
||||
> `pnpm dev` 不再启动 Plaza;如果需要联动 Plaza,请显式运行 `pnpm dev:all`,或者单独运行 `pnpm dev:plaza` / `pnpm start:plaza`。
|
||||
> Plaza 专用脚本的源码默认指向同级仓库 `../test-memindplaza/app/plaza`;如你的目录不同,请用 `PLAZA_APP_DIR` 覆盖。
|
||||
> 生产 / 测试 / 预览隔离仍单独看 [生产 / 测试 / 预览隔离规程](./service-isolation-runbook.md)。
|
||||
|
||||
`pnpm dev` 启动后,用 **127.0.0.1 + 端口** 访问:
|
||||
`pnpm dev` 启动后,用 **127.0.0.1 + 端口** 访问本仓库自己的服务:
|
||||
|
||||
| 服务 | 地址 |
|
||||
|------|------|
|
||||
| MindSpace H5 | http://127.0.0.1:5173/?preview=mindspace |
|
||||
| Plaza | http://127.0.0.1:3001/plaza |
|
||||
| Ops 审核后台 | http://127.0.0.1:3002/ops/ |
|
||||
| API / Portal | http://127.0.0.1:8081 |
|
||||
| memind_adm | http://127.0.0.1:8082 |
|
||||
| Plaza | http://127.0.0.1:3001/plaza |
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
cp .env.example .env
|
||||
PLAZA_APP_DIR=/path/to/plaza pnpm dev
|
||||
pnpm dev
|
||||
pnpm open:local-test # 浏览器打开 H5
|
||||
```
|
||||
|
||||
在生产机器上做预览时,请使用测试目录和测试端口:
|
||||
需要旧式全栈联动时,改用:
|
||||
|
||||
```bash
|
||||
cd /Users/john/Project/test/Memind
|
||||
H5_PORT=18081 \
|
||||
VITE_PORT=15173 \
|
||||
ADMIN_PORT=18082 \
|
||||
PLAZA_PORT=13001 \
|
||||
OPS_PORT=13002 \
|
||||
H5_PUBLIC_BASE_URL=http://127.0.0.1:15173 \
|
||||
VITE_MINDSPACE_BASE=http://127.0.0.1:15173 \
|
||||
pnpm dev
|
||||
pnpm dev:all
|
||||
```
|
||||
|
||||
预览地址:`http://127.0.0.1:15173/?preview=mindspace`。
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 默认 | 说明 |
|
||||
@@ -45,9 +38,9 @@ pnpm dev
|
||||
| `OPS_PORT` | 3002 | Ops SPA |
|
||||
| `ADMIN_PORT` | 8082 | memind_adm |
|
||||
| `H5_PUBLIC_BASE_URL` | http://127.0.0.1:5173 | 公开链接基址 |
|
||||
| `PLAZA_APP_DIR` | ../tkmind_go/ui/plaza | Plaza 源码路径 |
|
||||
| `PLAZA_APP_DIR` | ../test-memindplaza/app/plaza | Plaza 专用脚本的源码路径(`pnpm dev:plaza` / `pnpm start:plaza` / `pnpm dev:all`) |
|
||||
|
||||
Plaza 公网部署见 [plaza-local.md](./plaza-local.md)。
|
||||
Plaza 本地开发说明见 [plaza-local.md](./plaza-local.md)。生产发布、同步与回滚不要在这里处理,统一看 [生产更新发布指南](./release-deploy.md)。
|
||||
|
||||
## 公众号 Agent 调试
|
||||
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
# Memind 打通 AI Mind 项目评估报告
|
||||
|
||||
日期:2026-06-24
|
||||
范围:`/Users/john/PycharmProjects/test/test-memind` 与 `/Users/john/PycharmProjects/ai_mind`
|
||||
状态:只读评估结论整理,后续可继续深化方案与实施拆解
|
||||
|
||||
## 1. 结论
|
||||
|
||||
Memind 可以与 AI Mind 项目关联,而且关联价值很高。
|
||||
|
||||
推荐方向不是把两个项目合并,也不是直接共享数据库表,而是让 Memind 继续作为入口、账号、MindSpace、H5 体验和代理层,让 AI Mind 作为长期记忆、数字人格、认知画像、人生流与 persona runtime 的认知后端。
|
||||
|
||||
一句话概括:
|
||||
|
||||
> Memind 负责人机入口,AI Mind 负责脑和性格。
|
||||
|
||||
综合判断:
|
||||
|
||||
- 可行性:8/10
|
||||
- 推荐度:9/10
|
||||
- 直接合并推荐度:2/10
|
||||
|
||||
最合理的路线是先做轻量桥接,再做记忆回流,最后做深度融合。
|
||||
|
||||
## 2. 当前项目角色判断
|
||||
|
||||
### 2.1 Memind 的现状
|
||||
|
||||
Memind 当前更像 H5 入口、门户、用户空间、会话代理和 MindSpace 体验层。
|
||||
|
||||
它已有:
|
||||
|
||||
- 用户体系:`h5_users`
|
||||
- Agent 会话归属:`h5_user_sessions`
|
||||
- TKMind API proxy
|
||||
- Goose/agent session 转发链路
|
||||
- MindSpace 页面、资料、发布、广场、微信接入、计费等业务能力
|
||||
- 轻量用户偏好画像:`.tkmind-profile.json`
|
||||
|
||||
其中 `.tkmind-profile.json` 更像面向 Goose 会话的用户偏好卡片,字段包括语言、回复风格、偏好列表等。它可以提供基础记忆提示,但不是完整长期认知系统。
|
||||
|
||||
### 2.2 AI Mind 的现状
|
||||
|
||||
AI Mind 是一个更完整的认知与数字人格后端。
|
||||
|
||||
它已有:
|
||||
|
||||
- FastAPI `/api/v1/*` API 体系
|
||||
- `chat`、`memory`、`profile`、`persona`、`life_stream`、`coremind` 等正式路由
|
||||
- 长期记忆:`memory_items`、`memory_chunks`、`memory_vectors`
|
||||
- 用户认知画像:`user_cognitive_profile`
|
||||
- 数字人体系:`digital_personas`、`persona_conversations`、`persona_messages`
|
||||
- 数字人画像、知识库、技能、反思、自我核心、自治策略
|
||||
- 人生流 L1/L2/Phase3、profile evidence、memory bridge
|
||||
- Persona external API,可供外部系统调用数字人对话与画像
|
||||
|
||||
AI Mind 中的 persona context 已经能把用户画像、长期记忆、知识库和技能聚合成 prompt 上下文。这正是 Memind 想要获得的“真的有记忆、性格、特征”的能力基础。
|
||||
|
||||
## 3. 可关联的关键依据
|
||||
|
||||
### 3.1 Memind 已有代理层,适合接外部认知服务
|
||||
|
||||
Memind 服务端已经存在 TKMind API proxy。该代理层会处理:
|
||||
|
||||
- 登录态校验
|
||||
- 当前用户解析
|
||||
- 会话归属校验
|
||||
- 上游 API 转发
|
||||
- LLM provider 应用
|
||||
- 策略与能力检查
|
||||
|
||||
这意味着 AI Mind 可以作为外部认知服务接入,而不需要先改造整个 Memind 会话系统。
|
||||
|
||||
### 3.2 AI Mind 已有外部数字人接口
|
||||
|
||||
AI Mind 已有:
|
||||
|
||||
- `POST /api/v1/persona/external/chat`
|
||||
- `GET /api/v1/persona/external/profile`
|
||||
- `POST /api/v1/persona/external/profile`
|
||||
|
||||
这些接口通过 `X-API-Token` 识别外部启用的数字人,可返回人格化回复、conversation_id、persona_id、resource_summary、response_mode、画像与成长指标等。
|
||||
|
||||
这非常适合作为 Memind 的第一阶段桥接入口。
|
||||
|
||||
### 3.3 AI Mind 已有人生流投递接口
|
||||
|
||||
AI Mind 的 life_stream 支持:
|
||||
|
||||
- JWT 鉴权
|
||||
- 或 `X-Life-Stream-Ingest-Key + X-Life-Stream-User-Id` 机机投递
|
||||
|
||||
Memind 可以把重要对话、MindSpace 页面、资料摘要、微信消息摘要、用户行为事件投递为 life_stream event,由 AI Mind 进一步生成分析、证据、记忆、画像和 persona pipeline。
|
||||
|
||||
## 4. 推荐架构
|
||||
|
||||
推荐采用三层桥接架构:
|
||||
|
||||
```text
|
||||
用户
|
||||
|
|
||||
v
|
||||
Memind H5 / MindSpace / 微信 / Plaza
|
||||
|
|
||||
v
|
||||
Memind Bridge Layer
|
||||
|-- 调用 AI Mind persona external chat
|
||||
|-- 投递 AI Mind life_stream events
|
||||
|-- 拉取 AI Mind persona profile / memory summary
|
||||
|
|
||||
v
|
||||
AI Mind
|
||||
|-- 用户画像
|
||||
|-- 长期记忆
|
||||
|-- 数字人格
|
||||
|-- 人生流
|
||||
|-- coremind / runtime
|
||||
```
|
||||
|
||||
职责边界:
|
||||
|
||||
- Memind 保留入口、账号、空间、页面、发布、微信、广场、计费和 agent session。
|
||||
- AI Mind 提供记忆、人格、认知画像、数字人对话、人生流分析和长期沉淀。
|
||||
- Bridge Layer 负责身份映射、Token 管理、调用限流、错误降级和审计。
|
||||
|
||||
## 5. 最小可验证版本
|
||||
|
||||
最小 MVP 可以这样做:
|
||||
|
||||
1. 在 Memind 配置 AI Mind 地址,例如 `AI_MIND_BASE_URL=http://127.0.0.1:18000`。
|
||||
2. 为 Memind 用户绑定一个 AI Mind 用户和默认 persona。
|
||||
3. 保存该 persona 的 external token。
|
||||
4. Memind 新增一个“人格对话/记忆增强”调用:
|
||||
- `POST /api/v1/persona/external/chat`
|
||||
- Header: `X-API-Token: <external_token>`
|
||||
- Body: `message`、`conversation_id`、`external_user_id`
|
||||
5. Memind 异步投递重要事件:
|
||||
- `POST /api/v1/life-stream/events`
|
||||
- Header: `X-Life-Stream-Ingest-Key`
|
||||
- Header: `X-Life-Stream-User-Id`
|
||||
6. Memind 拉取画像:
|
||||
- `GET /api/v1/persona/external/profile`
|
||||
7. 在前端展示数字人的成长阶段、记忆分、技能分、画像摘要等。
|
||||
|
||||
这个版本可以在不破坏现有 Goose agent、计费、微信和 MindSpace 链路的情况下验证核心价值。
|
||||
|
||||
## 6. 身份映射设计建议
|
||||
|
||||
这是整个集成最关键的部分。
|
||||
|
||||
Memind 用户 ID 是 UUID 字符串:
|
||||
|
||||
```text
|
||||
h5_users.id CHAR(36)
|
||||
```
|
||||
|
||||
AI Mind 用户 ID 是自增整数:
|
||||
|
||||
```text
|
||||
users.id Integer
|
||||
```
|
||||
|
||||
因此不能直接共享 user_id,也不建议互相引用对方数据库外键。
|
||||
|
||||
建议增加一张 Memind 侧或独立桥接侧映射表:
|
||||
|
||||
```text
|
||||
memind_ai_mind_bindings
|
||||
```
|
||||
|
||||
建议字段:
|
||||
|
||||
- `id`
|
||||
- `memind_user_id`
|
||||
- `ai_mind_user_id`
|
||||
- `default_persona_id`
|
||||
- `persona_external_token`
|
||||
- `external_user_id`
|
||||
- `enabled`
|
||||
- `scopes`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
- `last_sync_at`
|
||||
|
||||
其中:
|
||||
|
||||
- `memind_user_id` 对应 `h5_users.id`
|
||||
- `ai_mind_user_id` 对应 AI Mind `users.id`
|
||||
- `default_persona_id` 对应 AI Mind `digital_personas.id`
|
||||
- `persona_external_token` 用于调用 `/api/v1/persona/external/chat`
|
||||
- `external_user_id` 可以稳定使用 `memind:<h5_user_id>`,避免多端混淆
|
||||
- `scopes` 可控制允许 chat、profile、life_stream、memory_read 等能力
|
||||
|
||||
## 7. 推荐分阶段路线
|
||||
|
||||
### 阶段一:轻量桥接
|
||||
|
||||
目标:先让 Memind 能调用 AI Mind 数字人,并能读取数字人画像。
|
||||
|
||||
动作:
|
||||
|
||||
- 新增 AI Mind bridge client
|
||||
- 新增用户绑定配置
|
||||
- 增加 persona external chat 调用
|
||||
- 增加 persona profile 拉取
|
||||
- 前端提供一个最小入口或隐藏实验入口
|
||||
|
||||
收益:
|
||||
|
||||
- 低风险
|
||||
- 快速验证“人格化回复”和“画像展示”
|
||||
- 不影响原有 agent session 主链路
|
||||
|
||||
### 阶段二:记忆回流
|
||||
|
||||
目标:让 Memind 的真实用户行为成为 AI Mind 长期记忆和画像原料。
|
||||
|
||||
动作:
|
||||
|
||||
- 把重要对话摘要投递到 life_stream
|
||||
- 把 MindSpace 页面保存、编辑、发布事件投递到 life_stream
|
||||
- 把微信重要消息摘要投递到 life_stream
|
||||
- 开启 AI Mind 的 life_stream analysis、memory bridge、profile evidence merge
|
||||
|
||||
收益:
|
||||
|
||||
- AI Mind 开始持续学习用户
|
||||
- 数字人能逐渐拥有更稳定的记忆、性格和特征
|
||||
- Memind 的空间内容不再只是文件,而是可认知的生命流材料
|
||||
|
||||
### 阶段三:深度融合
|
||||
|
||||
目标:让 AI Mind 的人格与记忆反向增强 Memind 的 Goose/agent 会话。
|
||||
|
||||
动作:
|
||||
|
||||
- Memind agent session 启动或恢复时,从 AI Mind 拉 persona context
|
||||
- 把 persona context 注入 Goose 会话上下文
|
||||
- 支持用户切换不同 persona 作为对话人格层
|
||||
- 支持对话后的高价值内容自动回写 AI Mind
|
||||
|
||||
收益:
|
||||
|
||||
- Memind 主对话获得长期记忆
|
||||
- 不同数字人格可在同一入口中体现不同性格和能力
|
||||
- MindSpace、Agent、微信、人生流形成闭环
|
||||
|
||||
## 8. 不建议的方案
|
||||
|
||||
### 8.1 不建议直接合并项目
|
||||
|
||||
Memind 是 Node/React/Express/Vite 体系,AI Mind 是 Python/FastAPI/Vue 体系。直接合并会引入运行时、依赖、部署、鉴权、数据库迁移等复杂问题。
|
||||
|
||||
### 8.2 不建议直接共享数据库表
|
||||
|
||||
两边用户 ID、权限模型、数据生命周期、业务边界不同。直接共享表容易造成:
|
||||
|
||||
- 用户串号
|
||||
- Token 泄漏
|
||||
- 权限误用
|
||||
- 数据一致性问题
|
||||
- 后续迁移困难
|
||||
|
||||
### 8.3 不建议立刻替换 Memind 主聊天链路
|
||||
|
||||
Memind 当前 `/sessions/:id/reply` 链路已有:
|
||||
|
||||
- 会话归属
|
||||
- 策略控制
|
||||
- 能力检查
|
||||
- 计费
|
||||
- SSE
|
||||
- LLM provider fallback
|
||||
|
||||
第一阶段应采用旁路增强,而不是直接接管全部聊天。
|
||||
|
||||
## 9. 主要风险与缓解
|
||||
|
||||
### 9.1 身份映射风险
|
||||
|
||||
风险:Memind 用户和 AI Mind 用户映射错误,导致记忆或数字人串号。
|
||||
|
||||
缓解:
|
||||
|
||||
- 使用显式绑定表
|
||||
- 所有调用带 `external_user_id`
|
||||
- 调用前校验 binding enabled 和 scopes
|
||||
- 管理后台可查看与解绑
|
||||
|
||||
### 9.2 Token 安全风险
|
||||
|
||||
风险:AI Mind persona external token 泄漏或配置错。
|
||||
|
||||
缓解:
|
||||
|
||||
- Token 仅服务端保存
|
||||
- 前端不暴露 external token
|
||||
- 绑定表支持禁用和轮换
|
||||
- 所有调用写审计日志
|
||||
|
||||
### 9.3 记忆污染风险
|
||||
|
||||
风险:低价值、错误、临时内容进入长期记忆。
|
||||
|
||||
缓解:
|
||||
|
||||
- life_stream 投递先标记 source、privacy_class、metadata
|
||||
- 只投递摘要或高价值事件
|
||||
- 用户显式删除/忘记时要回写 AI Mind
|
||||
- 长期记忆写入应走 AI Mind 已有置信度和候选审核机制
|
||||
|
||||
### 9.4 体验延迟风险
|
||||
|
||||
风险:persona deep mode 可能比普通聊天慢。
|
||||
|
||||
缓解:
|
||||
|
||||
- 首阶段作为独立人格入口,不阻塞主 agent
|
||||
- 对短消息允许 style_only 或降级
|
||||
- 缓存 profile
|
||||
- 后台异步投递 life_stream
|
||||
|
||||
### 9.5 服务可用性风险
|
||||
|
||||
风险:AI Mind 不可用时影响 Memind。
|
||||
|
||||
缓解:
|
||||
|
||||
- Bridge client 设置超时
|
||||
- AI Mind 调用失败时降级为普通 Memind/Goose 回复
|
||||
- UI 显示“记忆增强暂不可用”
|
||||
- 不让 AI Mind 成为 Memind 基础功能的强依赖
|
||||
|
||||
## 10. 后续分析问题
|
||||
|
||||
后续可继续深入以下问题:
|
||||
|
||||
1. Memind 用户与 AI Mind 用户是否已有线上对应关系?
|
||||
2. 是否要为每个 Memind 用户自动创建 AI Mind 用户和默认 persona?
|
||||
3. 默认 persona 是“用户的数字分身”,还是“陪伴型助手人格”?
|
||||
4. 哪些 Memind 事件应该进入 life_stream?
|
||||
5. 哪些内容只做短期上下文,不进入长期记忆?
|
||||
6. 用户如何查看、删除、纠正 AI Mind 记住的内容?
|
||||
7. 是否需要在 Memind 管理后台增加 AI Mind 绑定和诊断页?
|
||||
8. MVP 是先做个人实验入口,还是直接作为正式功能灰度?
|
||||
9. 是否要把 MindSpace 页面摘要同步为 persona knowledge,而不仅是 life_stream?
|
||||
10. 是否需要单独的隐私分级和用户授权开关?
|
||||
|
||||
## 11. 建议的下一步
|
||||
|
||||
建议下一步先做一份实施设计,不急着写代码:
|
||||
|
||||
- 定义 binding 表结构
|
||||
- 定义 AI Mind bridge client API
|
||||
- 定义 persona chat 调用协议
|
||||
- 定义 life_stream event 映射规范
|
||||
- 定义前端入口和降级体验
|
||||
- 定义审计日志和安全边界
|
||||
- 列出 MVP 验收用例
|
||||
|
||||
完成这些后,再进入代码实现会更稳。
|
||||
|
||||
@@ -0,0 +1,646 @@
|
||||
# memindadm 内 Goose 网关策略中心设计
|
||||
|
||||
> **定位:** `memindadm` 后台内的一个功能模块,不是独立治理平台,也不是 Plaza 审核后台的一部分。
|
||||
>
|
||||
> **目标:** 做一个可配置、可审计、可扩展的 Goose 网关策略中心。
|
||||
>
|
||||
> **原则:** 不做大而全的中台,不引入复杂流程编排,先把策略控制、路由分发、审计留痕做扎实。
|
||||
>
|
||||
> **边界:** 该模块只做旁路审计和策略决策,不改变现有 Goose 服务的核心行为,不把 Goose 变成被动依赖,也不在第一阶段强制改造现有 Goose 服务链路。它归属 `memindadm` 的用户管理后台,不归属 Plaza 审核后台。
|
||||
>
|
||||
> **统一要求:** `Goose / Aider / OpenHands` 的 LLM 配置必须收敛到 `memindadm`,后台只保留一套统一模型配置与执行器绑定,不允许给 Goose 单独再开一套独立 LLM 设置。
|
||||
|
||||
## 1. 背景
|
||||
|
||||
现有 `memindadm` 后台已经承担了平台管理、能力配置、审计查看等职责。基于这套后台能力,可以新增一个面向 Goose 调用链路的策略模块,用来统一管理:
|
||||
|
||||
- 输入内容过滤
|
||||
- 敏感词与敏感表达屏蔽
|
||||
- 输出话术约束
|
||||
- 执行器分配策略
|
||||
- 高风险操作拦截与人工确认
|
||||
- 调用审计与复盘
|
||||
|
||||
这里的核心不是“再做一个后台”,而是把 Goose 的调用前、调用中、调用后策略,纳入 `memindadm` 统一管理。
|
||||
|
||||
### 1.1 非侵入式要求
|
||||
|
||||
这一版必须满足两个硬约束:
|
||||
|
||||
1. 现有 Goose 服务仍然可以按原方式独立运行。
|
||||
2. `memindadm` 只在调用入口、审计链路、策略配置层提供旁路能力,不要求 Goose 原服务先完成深度改造。
|
||||
|
||||
换句话说:
|
||||
|
||||
- 先接策略,不先改服务。
|
||||
- 先留审计,不先改执行。
|
||||
- 先做可观察性,不先做强制接管。
|
||||
|
||||
如果后续要把 `memindadm` 的策略真正注入到 Goose 执行链路里,再单独做一个可控的接入阶段。
|
||||
|
||||
### 1.2 统一模型配置要求
|
||||
|
||||
模型配置必须只有一个控制平面:
|
||||
|
||||
- `memindadm` 维护 Provider、API Key、Base URL、模型列表、默认模型。
|
||||
- `Goose`、`Aider`、`OpenHands` 只从 `memindadm` 读取模型绑定。
|
||||
- 后台不再出现“Goose 单独配置 LLM”的入口。
|
||||
- 某个执行器如果暂时不用,可以禁用绑定,但不能绕开 `memindadm` 单独配。
|
||||
|
||||
迁移期间如果需要,可以先停掉原来的独立 Goose 服务配置,让它完全切到 `memindadm` 的统一配置上。
|
||||
|
||||
## 2. 设计目标
|
||||
|
||||
这个模块要解决的事情很明确:
|
||||
|
||||
1. 当前任务是否允许执行。
|
||||
2. 输入内容是否包含敏感信息。
|
||||
3. 输出话术是否符合产品约束。
|
||||
4. 当前任务应该由 Goose 自处理、Aider 执行,还是 OpenHands 执行。
|
||||
5. 是否需要人工确认。
|
||||
6. 执行结果是否需要记录、复核或回滚。
|
||||
7. `Goose / Aider / OpenHands` 是否使用同一 Provider 下的不同模型绑定。
|
||||
|
||||
最终效果是:
|
||||
|
||||
- `memindadm` = 策略配置与审计后台
|
||||
- `memindadm` = 统一模型与策略控制台
|
||||
- Goose Gateway = 策略执行入口
|
||||
- Aider / OpenHands = 具体执行器
|
||||
- Audit Log = 全链路留痕
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U["用户 / 前端 / 管理员"] --> A["memindadm 后台"]
|
||||
A --> P["Goose 网关策略中心"]
|
||||
P --> G["Goose Gateway"]
|
||||
G --> E["策略判断引擎"]
|
||||
E --> F["内容过滤规则"]
|
||||
E --> S["话术约束规则"]
|
||||
E --> R["执行器分配规则"]
|
||||
E --> H["高风险操作规则"]
|
||||
E --> M["人工确认规则"]
|
||||
E --> X["执行器路由"]
|
||||
X --> G1["Goose 自处理"]
|
||||
X --> G2["Aider 执行"]
|
||||
X --> G3["OpenHands 执行"]
|
||||
G1 --> L["结果 / Diff / 审计日志"]
|
||||
G2 --> L
|
||||
G3 --> L
|
||||
L --> A
|
||||
```
|
||||
|
||||
### 架构解读
|
||||
|
||||
- `memindadm` 负责配置和查看,不直接承担执行。
|
||||
- Goose Gateway 是统一入口,所有 Goose 相关调用都从这里经过。
|
||||
- 策略判断引擎先做规则匹配,再决定是否执行、交给谁执行、是否要人工确认。
|
||||
- 执行结果回流到 `memindadm` 审计中心。
|
||||
|
||||
## 4. 菜单结构
|
||||
|
||||
建议在 `memindadm` 下新增一个一级菜单:
|
||||
|
||||
- `Goose 网关`
|
||||
|
||||
下面放 5 个子菜单即可,保持克制:
|
||||
|
||||
1. 内容过滤
|
||||
2. 敏感词
|
||||
3. 话术约束
|
||||
4. 执行策略
|
||||
5. 调用日志
|
||||
|
||||
同时建议保留或升级原有 `LLM 配置` 页面为 `统一模型中心`,专门管理:
|
||||
|
||||
- Provider
|
||||
- API Key
|
||||
- Base URL
|
||||
- 模型列表
|
||||
- 默认模型
|
||||
- 执行器绑定
|
||||
|
||||
这个页面要同时服务 `Goose / Aider / OpenHands`,不是只给 Goose 用。
|
||||
|
||||
如果后续确实需要,再补:
|
||||
|
||||
- 风险规则
|
||||
- 人工确认
|
||||
- 策略测试
|
||||
|
||||
第一版不建议拆太多页,避免后台变复杂。
|
||||
|
||||
## 5. 核心模块
|
||||
|
||||
### 5.1 内容过滤规则
|
||||
|
||||
内容过滤用于处理三类文本:
|
||||
|
||||
- `input`:用户输入
|
||||
- `prompt`:发给 Goose / Aider / OpenHands 的任务内容
|
||||
- `output`:最终返回给用户的内容
|
||||
|
||||
推荐支持的匹配方式:
|
||||
|
||||
- 关键词匹配
|
||||
- 正则表达式匹配
|
||||
- 敏感字段检测
|
||||
- 文件路径检测
|
||||
- 高危命令检测
|
||||
- 代码操作风险检测
|
||||
|
||||
推荐动作:
|
||||
|
||||
- `pass`:允许通过
|
||||
- `mask`:脱敏替换
|
||||
- `block`:阻断执行
|
||||
- `confirm`:需要人工确认
|
||||
- `log_only`:只记录不拦截
|
||||
|
||||
示例:
|
||||
|
||||
```yaml
|
||||
rule_id: filter_001
|
||||
name: 禁止输出密钥
|
||||
target: output
|
||||
type: regex
|
||||
pattern: "(sk-[a-zA-Z0-9]{20,}|AKIA[0-9A-Z]{16})"
|
||||
action: mask
|
||||
replacement: "[已屏蔽密钥]"
|
||||
level: high
|
||||
enabled: true
|
||||
```
|
||||
|
||||
### 5.2 敏感词管理
|
||||
|
||||
敏感词不建议只做单一词库,最好分层:
|
||||
|
||||
- 基础敏感词:明确禁止出现的词
|
||||
- 业务敏感词:项目、客户、合同、价格、内部系统等
|
||||
- 技术敏感词:密钥、Token、数据库连接串、服务器地址、生产账号等
|
||||
|
||||
对于技术类内容,不建议一律 `block`。更合理的策略是:
|
||||
|
||||
- 普通讨论:允许
|
||||
- 包含真实值:脱敏
|
||||
- 涉及生产操作:人工确认
|
||||
- 要求输出密钥:阻断
|
||||
|
||||
### 5.3 话术约束
|
||||
|
||||
话术约束主要用于控制 Goose 的表达方式,避免输出不符合产品定位的内容。
|
||||
|
||||
建议分三类:
|
||||
|
||||
1. 固定禁止话术
|
||||
2. 固定推荐话术
|
||||
3. 场景化话术模板
|
||||
|
||||
示例模板:
|
||||
|
||||
```yaml
|
||||
scene: coding_task_result
|
||||
name: 编码任务完成话术
|
||||
template:
|
||||
- 执行器:{executor}
|
||||
- 修改范围:{changed_files}
|
||||
- 测试结果:{test_result}
|
||||
- 风险提示:{risk_summary}
|
||||
- 下一步建议:{next_action}
|
||||
```
|
||||
|
||||
### 5.4 执行器分配策略
|
||||
|
||||
执行器建议分成 5 类:
|
||||
|
||||
- `goose`:分析、设计、轻量编排
|
||||
- `aider`:小范围代码修改、补丁式修复
|
||||
- `openhands`:复杂开发任务、多文件改造、仓库探索、命令执行
|
||||
- `manual`:需要人工确认
|
||||
- `reject`:拒绝执行
|
||||
|
||||
建议第一阶段使用规则引擎,不做复杂模型决策。
|
||||
|
||||
示例策略:
|
||||
|
||||
```yaml
|
||||
policy_id: route_001
|
||||
name: 小范围代码修改走 Aider
|
||||
conditions:
|
||||
task_type: code_change
|
||||
max_files: 3
|
||||
requires_browser: false
|
||||
requires_long_running_env: false
|
||||
risk_level: low
|
||||
executor: aider
|
||||
priority: 100
|
||||
enabled: true
|
||||
---
|
||||
policy_id: route_002
|
||||
name: 复杂仓库任务走 OpenHands
|
||||
conditions:
|
||||
task_type:
|
||||
- feature_dev
|
||||
- bug_fix_complex
|
||||
- repo_refactor
|
||||
min_files: 4
|
||||
requires_command_execution: true
|
||||
executor: openhands
|
||||
priority: 90
|
||||
enabled: true
|
||||
---
|
||||
policy_id: route_003
|
||||
name: 只分析不改代码走 Goose
|
||||
conditions:
|
||||
task_type:
|
||||
- architecture_design
|
||||
- code_review
|
||||
- requirement_analysis
|
||||
write_permission_required: false
|
||||
executor: goose
|
||||
priority: 80
|
||||
enabled: true
|
||||
---
|
||||
policy_id: route_004
|
||||
name: 高风险操作需要人工确认
|
||||
conditions:
|
||||
risk_keywords:
|
||||
- 删除数据库
|
||||
- 生产环境
|
||||
- 支付接口
|
||||
- 权限系统
|
||||
- 用户数据
|
||||
risk_level: high
|
||||
executor: manual
|
||||
priority: 200
|
||||
enabled: true
|
||||
```
|
||||
|
||||
优先级原则:
|
||||
|
||||
- 高风险规则优先
|
||||
- 阻断规则优先
|
||||
- 人工确认优先
|
||||
- 明确执行器规则优先
|
||||
- 默认 Goose 自处理
|
||||
|
||||
### 5.5 任务识别器
|
||||
|
||||
Goose Gateway 在执行前需要先把用户任务识别成结构化结果。
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"task_type": "feature_dev",
|
||||
"risk_level": "medium",
|
||||
"requires_code_change": true,
|
||||
"requires_command_execution": true,
|
||||
"estimated_files": 5,
|
||||
"requires_browser": false,
|
||||
"target_repo": "memind-h5",
|
||||
"suggested_executor": "openhands"
|
||||
}
|
||||
```
|
||||
|
||||
推荐流程:
|
||||
|
||||
1. 用户输入
|
||||
2. LLM 初步识别任务类型
|
||||
3. 规则引擎二次校验
|
||||
4. 匹配执行器策略
|
||||
5. 生成执行计划
|
||||
|
||||
### 5.6 高风险操作控制
|
||||
|
||||
高风险类型建议内置:
|
||||
|
||||
- 生产数据库操作
|
||||
- 删除文件或目录
|
||||
- 批量修改用户数据
|
||||
- 支付、充值、订单相关逻辑
|
||||
- 权限、登录、Token、密钥相关逻辑
|
||||
- 服务器部署、重启、停止服务
|
||||
- 对外发送消息、邮件、公众号发布
|
||||
|
||||
这些动作统一进入人工确认:
|
||||
|
||||
```text
|
||||
confirm_required = true
|
||||
```
|
||||
|
||||
确认内容应包含:
|
||||
|
||||
- 任务说明
|
||||
- 执行器
|
||||
- 目标仓库
|
||||
- 计划修改文件
|
||||
- 预计执行命令
|
||||
- 风险点
|
||||
- 回滚建议
|
||||
|
||||
## 6. 调用流程
|
||||
|
||||
### 6.1 普通任务
|
||||
|
||||
```text
|
||||
用户提交任务
|
||||
↓
|
||||
Goose Gateway 接收
|
||||
↓
|
||||
内容过滤
|
||||
↓
|
||||
任务识别
|
||||
↓
|
||||
执行器策略匹配
|
||||
↓
|
||||
调用 Aider / OpenHands / Goose
|
||||
↓
|
||||
结果过滤
|
||||
↓
|
||||
话术约束
|
||||
↓
|
||||
返回用户
|
||||
↓
|
||||
写入审计日志
|
||||
```
|
||||
|
||||
### 6.2 高风险任务
|
||||
|
||||
```text
|
||||
用户提交任务
|
||||
↓
|
||||
内容过滤
|
||||
↓
|
||||
命中高风险规则
|
||||
↓
|
||||
生成执行计划
|
||||
↓
|
||||
进入人工确认
|
||||
↓
|
||||
管理员确认
|
||||
↓
|
||||
执行器执行
|
||||
↓
|
||||
结果审计
|
||||
↓
|
||||
返回用户
|
||||
```
|
||||
|
||||
## 7. 审计日志
|
||||
|
||||
每一次调用都必须记录。
|
||||
|
||||
建议字段:
|
||||
|
||||
- `request_id`
|
||||
- `user_id`
|
||||
- 原始输入
|
||||
- 过滤结果
|
||||
- 命中规则
|
||||
- 任务类型
|
||||
- 风险等级
|
||||
- 选择的执行器
|
||||
- 执行参数
|
||||
- 执行日志
|
||||
- 修改文件
|
||||
- diff 摘要
|
||||
- 测试结果
|
||||
- 最终输出
|
||||
- 创建时间
|
||||
- 完成时间
|
||||
- 执行状态
|
||||
|
||||
审计日志第一阶段可以先不做复杂可视化,但数据库结构要先预留。
|
||||
|
||||
## 8. 表结构建议
|
||||
|
||||
### `goose_policy_rule`
|
||||
|
||||
- `id`
|
||||
- `rule_name`
|
||||
- `rule_type`
|
||||
- `target`
|
||||
- `match_type`
|
||||
- `pattern`
|
||||
- `action`
|
||||
- `risk_level`
|
||||
- `priority`
|
||||
- `enabled`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
|
||||
### `goose_sensitive_word`
|
||||
|
||||
- `id`
|
||||
- `group_name`
|
||||
- `word`
|
||||
- `scope`
|
||||
- `action`
|
||||
- `enabled`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
|
||||
### `goose_route_policy`
|
||||
|
||||
- `id`
|
||||
- `policy_name`
|
||||
- `task_type`
|
||||
- `conditions_json`
|
||||
- `executor`
|
||||
- `priority`
|
||||
- `enabled`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
|
||||
### `goose_execution_log`
|
||||
|
||||
- `id`
|
||||
- `request_id`
|
||||
- `user_id`
|
||||
- `task_type`
|
||||
- `risk_level`
|
||||
- `executor`
|
||||
- `input_text`
|
||||
- `filtered_input`
|
||||
- `matched_rules_json`
|
||||
- `execution_status`
|
||||
- `execution_summary`
|
||||
- `diff_summary`
|
||||
- `test_result`
|
||||
- `llm_provider`
|
||||
- `llm_model`
|
||||
- `created_at`
|
||||
- `finished_at`
|
||||
|
||||
### `goose_manual_confirm`
|
||||
|
||||
- `id`
|
||||
- `request_id`
|
||||
- `confirm_type`
|
||||
- `risk_summary`
|
||||
- `planned_action`
|
||||
- `status`
|
||||
- `operator_id`
|
||||
- `confirmed_at`
|
||||
- `created_at`
|
||||
|
||||
## 9. `memindadm` 后台页面建议
|
||||
|
||||
### 9.1 内容过滤规则页
|
||||
|
||||
字段:
|
||||
|
||||
- 规则名称
|
||||
- 检测对象
|
||||
- 匹配方式
|
||||
- 关键词 / 正则
|
||||
- 处理动作
|
||||
- 风险等级
|
||||
- 是否启用
|
||||
- 优先级
|
||||
|
||||
### 9.2 敏感词管理页
|
||||
|
||||
字段:
|
||||
|
||||
- 词库分组
|
||||
- 敏感词
|
||||
- 作用范围
|
||||
- 处理动作
|
||||
- 是否启用
|
||||
|
||||
### 9.3 话术约束页
|
||||
|
||||
字段:
|
||||
|
||||
- 场景
|
||||
- 禁止话术
|
||||
- 推荐话术
|
||||
- 输出模板
|
||||
- 是否启用
|
||||
|
||||
### 9.4 执行器分配策略页
|
||||
|
||||
字段:
|
||||
|
||||
- 策略名称
|
||||
- 任务类型
|
||||
- 条件配置
|
||||
- 目标执行器
|
||||
- 风险等级
|
||||
- 优先级
|
||||
- 是否启用
|
||||
|
||||
### 9.5 调用日志页
|
||||
|
||||
字段:
|
||||
|
||||
- 请求时间
|
||||
- 用户
|
||||
- 任务类型
|
||||
- 命中规则
|
||||
- 执行器
|
||||
- 风险等级
|
||||
- 执行状态
|
||||
- 详情查看
|
||||
|
||||
## 10. MVP 范围
|
||||
|
||||
第一阶段只做小而精,不做复杂流程引擎。
|
||||
|
||||
### 必须做
|
||||
|
||||
1. 内容过滤规则配置
|
||||
2. 敏感词配置
|
||||
3. 执行器分配策略配置
|
||||
4. Goose 调用前策略判断
|
||||
5. Aider / OpenHands 路由选择
|
||||
6. 调用审计日志
|
||||
7. 统一模型中心
|
||||
|
||||
### 可以暂缓
|
||||
|
||||
1. 多级审批
|
||||
2. 复杂权限矩阵
|
||||
3. 策略版本管理
|
||||
4. 复杂可视化编排
|
||||
5. 自动回滚
|
||||
6. 多租户策略隔离
|
||||
|
||||
## 11. 推荐落地顺序
|
||||
|
||||
### 第一步:先做表和日志
|
||||
|
||||
先把规则、敏感词、执行器策略、日志表建起来。
|
||||
|
||||
### 第二步:做 Goose Gateway 策略判断
|
||||
|
||||
在 Goose 调用前增加统一入口:
|
||||
|
||||
```text
|
||||
before_execute(task)
|
||||
```
|
||||
|
||||
负责:
|
||||
|
||||
- 过滤输入
|
||||
- 识别任务
|
||||
- 判断风险
|
||||
- 选择执行器
|
||||
- 生成执行计划
|
||||
|
||||
### 第三步:接入 Aider
|
||||
|
||||
先支持小范围代码任务走 Aider,因为调用简单、成本低、见效快。
|
||||
|
||||
### 第四步:接入 OpenHands
|
||||
|
||||
再把复杂任务转给 OpenHands,让它负责仓库探索、多文件开发和命令执行。
|
||||
|
||||
### 第五步:把 Goose 也切到统一模型中心
|
||||
|
||||
Goose 不再使用单独的模型配置页面,而是直接读取 `memindadm` 的统一模型中心。必要时可以先停掉原来独立的 Goose LLM 配置,保证入口只有一个。
|
||||
|
||||
### 第六步:做 `memindadm` 后台页面
|
||||
|
||||
先做简单 CRUD,不追求复杂交互。
|
||||
|
||||
## 12. 预期效果
|
||||
|
||||
完成后,`memindadm` 会具备一套轻量级 Goose 网关治理能力:
|
||||
|
||||
- 可配置内容过滤
|
||||
- 可配置敏感词屏蔽
|
||||
- 可配置话术约束
|
||||
- 可配置执行器路由
|
||||
- 可拦截高风险操作
|
||||
- 可审计每次调用过程
|
||||
|
||||
这样 Goose 不再只是单一 Agent,而是变成一个可以统一调度 Aider、OpenHands、Claude Code 等工具的策略入口。
|
||||
|
||||
## 13. 最终定位
|
||||
|
||||
- `memindadm` = 用户与策略后台
|
||||
- `Goose Gateway` = Agent 编排入口
|
||||
- `Aider` = 小型代码修改执行器
|
||||
- `OpenHands` = 复杂代码任务执行器
|
||||
- `Goose` = 策略与分析执行器,模型同样来自统一模型中心
|
||||
- `Policy Center` = 安全与路由规则中心
|
||||
- `Audit Log` = 行为留痕与复盘中心
|
||||
|
||||
第一版建议在产品命名上保持克制,后台菜单直接叫:
|
||||
|
||||
- `Goose 网关`
|
||||
|
||||
只放这 5 个子菜单:
|
||||
|
||||
1. 内容过滤
|
||||
2. 敏感词
|
||||
3. 话术约束
|
||||
4. 执行策略
|
||||
5. 调用日志
|
||||
|
||||
这样产品上小,架构上完整,后续可以自然扩展。
|
||||
@@ -0,0 +1,86 @@
|
||||
# test-memind Portal 无源码迁移说明
|
||||
|
||||
## 目标
|
||||
|
||||
把 `test-memind` 的 Portal 先迁到“`103` 仅运行 runtime artifact,不再依赖源码树”的模式,同时严格保护这些持久数据:
|
||||
|
||||
- `MindSpace/`
|
||||
- `data/`
|
||||
- `users/`
|
||||
- `public/plaza-covers/`
|
||||
- `.env`
|
||||
- `.tailscale/`
|
||||
- `logs/`
|
||||
|
||||
## 已确认的生产差异
|
||||
|
||||
这些差异不应该再体现在源码目录里,而应该全部通过运行时配置保留:
|
||||
|
||||
1. 本地默认可以连本地数据库;生产实际使用 RDS。
|
||||
2. 本地 Goose 可以是单实例;生产 `103` 是双实例:
|
||||
- `TKMIND_API_TARGET=https://127.0.0.1:18006`
|
||||
- `TKMIND_API_TARGET_1=https://127.0.0.1:18007`
|
||||
3. 本地端口和外网入口可以不同;生产实际是:
|
||||
- Portal `127.0.0.1:8081`
|
||||
- Plaza `127.0.0.1:3001`
|
||||
- `105` 只做入口/转发,不承载主 Portal 源码运行
|
||||
4. 生产用户公开入口是 `https://g2.tkmind.cn`,不是裸 IP,也不是本地 localhost。
|
||||
5. 运维、发包、健康检查统一走固定公网地址,不再依赖 `10.10.*` 局域网:
|
||||
- `105`:`120.26.184.105`
|
||||
- `103 / Studio`:`58.38.22.103`
|
||||
|
||||
## 迁移原则
|
||||
|
||||
1. 构建发生在本机,不发生在 `103`。
|
||||
2. `103` 不再 `npm install`,不再 `npm run build`。
|
||||
3. `103` 只接收运行产物、继承持久目录、启动服务。
|
||||
4. 生产差异通过 `.env` 和启动环境注入,不通过“线上源码和本地源码不同”来兜底。
|
||||
5. 迁移前必须先打整包备份,且单独备份持久目录。
|
||||
6. 后续所有发布脚本默认都应优先直连 `58.38.22.103`,只有用户明确要求时才允许覆盖 `STUDIO_HOST`。
|
||||
|
||||
## Portal 运行产物
|
||||
|
||||
新增本地构建脚本:
|
||||
|
||||
```bash
|
||||
node scripts/build-portal-runtime.mjs
|
||||
```
|
||||
|
||||
产物目录:
|
||||
|
||||
```text
|
||||
.runtime/portal
|
||||
```
|
||||
|
||||
当前产物包含:
|
||||
|
||||
1. `dist/` 前端静态资源。
|
||||
2. `public/` 静态资源。
|
||||
3. `server.mjs` 后端单文件 runtime bundle。
|
||||
4. `mindspace-sandbox-mcp.mjs` sandbox-fs 扩展子进程入口(必须与 `server.mjs` 同目录)。
|
||||
5. `node_modules/` 运行依赖。
|
||||
6. `package.json`、`scripts/run-memind-portal-prod.sh` 与运行说明。
|
||||
|
||||
## 为什么本地/生产差异不会阻止无源码部署
|
||||
|
||||
因为这些差异本质上都属于运行时配置:
|
||||
|
||||
1. 数据库差异:
|
||||
本地和生产只是 `.env` 里的 `DATABASE_URL` / `MYSQL_*` 不同,artifact 不应该内置数据库地址。
|
||||
2. Goose 单例/双例差异:
|
||||
只是 `TKMIND_API_TARGET` / `TKMIND_API_TARGET_1` 的值不同,artifact 不应该写死实例数量。
|
||||
3. 105 转发到 103:
|
||||
这是入口层拓扑,不是 Portal 源码结构。只要 `103` 上的 Portal 监听端口不变,入口层可以保持不变。
|
||||
4. 端口差异:
|
||||
只是 `H5_PORT`、`PLAZA_PORT`、反代配置不同,不是源码必须驻留在 `103` 的理由。
|
||||
|
||||
## 生产发布入口
|
||||
|
||||
Portal 在 `103` 上必须只运行 runtime artifact,唯一合法发布命令:
|
||||
|
||||
```bash
|
||||
bash scripts/release-portal-runtime-prod.sh --dry-run
|
||||
bash scripts/release-portal-runtime-prod.sh
|
||||
```
|
||||
|
||||
`bash scripts/release-prod.sh` 已停用,不得再用于 Portal。
|
||||
@@ -0,0 +1,171 @@
|
||||
# OpenHands 安装说明
|
||||
|
||||
> 目标:先把 OpenHands 独立安装并跑起来,确认可用后,再接入 `memindadm` 的 Goose 网关策略中心。
|
||||
>
|
||||
> 原则:安装阶段不改动现有 Goose 服务,不影响当前生产/测试链路。
|
||||
|
||||
## 1. 推荐方案
|
||||
|
||||
如果你的目标是后续和 Goose 做集成,建议先用 **OpenHands CLI + GUI Server** 方式启动:
|
||||
|
||||
- 本地直接运行
|
||||
- 可挂载当前仓库目录
|
||||
- 便于后续做执行器接入验证
|
||||
|
||||
官方文档对 `openhands serve` 的说明是:它会通过 Docker 启动本地 GUI Server,支持挂载当前目录,适合直接对仓库做任务。
|
||||
|
||||
## 2. 前置条件
|
||||
|
||||
### 必需
|
||||
|
||||
- `uv`
|
||||
- Python 3.12+
|
||||
- Docker Desktop 已安装并运行
|
||||
- 可用的 LLM Provider / Model / API Key
|
||||
|
||||
### Mac 上建议额外确认
|
||||
|
||||
- Docker Desktop 的默认 socket 访问已开启
|
||||
- `docker ps` 可以正常执行
|
||||
|
||||
如果后面 `serve` 起不来,优先检查 Docker 是否在运行,以及 Docker Desktop 的相关网络设置。
|
||||
|
||||
## 3. 安装方式
|
||||
|
||||
### 方式 A:推荐,使用 `uv`
|
||||
|
||||
```bash
|
||||
uv tool install openhands --python 3.12
|
||||
```
|
||||
|
||||
安装完成后启动:
|
||||
|
||||
```bash
|
||||
openhands serve --mount-cwd
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `serve` 会启动 GUI Server
|
||||
- `--mount-cwd` 会把当前目录挂进 OpenHands 的工作区
|
||||
- 这样它可以直接面对你的仓库做任务
|
||||
- 首次启动后需要在界面里选择 LLM Provider、Model,并填入对应 API Key
|
||||
|
||||
如果你只想先验证能跑起来,也可以先不加 `--mount-cwd`:
|
||||
|
||||
```bash
|
||||
openhands serve
|
||||
```
|
||||
|
||||
### 方式 B:使用官方安装脚本
|
||||
|
||||
```bash
|
||||
curl -fsSL https://install.openhands.dev/install.sh | sh
|
||||
```
|
||||
|
||||
安装后同样可以启动:
|
||||
|
||||
```bash
|
||||
openhands serve --mount-cwd
|
||||
```
|
||||
|
||||
### 方式 C:Docker / Agent Canvas
|
||||
|
||||
如果你更偏向容器化,可按 OpenHands 的 Agent Canvas / Docker 文档走容器启动方式。这个方式适合未来把 OpenHands 作为更独立的执行环境来跑。
|
||||
|
||||
### 端口与资源
|
||||
|
||||
- GUI Server 默认会占用 `3000` 端口
|
||||
- Docker 镜像和运行时需要一定磁盘空间
|
||||
- 如果要跑 GPU,可以在 `serve` 时加 `--gpu`
|
||||
|
||||
## 4. Mac 最短安装路径
|
||||
|
||||
如果你现在是在 Mac 上直接装,我建议按这个顺序走:
|
||||
|
||||
### 4.1 安装 Docker Desktop
|
||||
|
||||
去 Docker 官方页面下载安装包,按芯片类型选择 Apple Silicon 或 Intel 版本。
|
||||
|
||||
- 安装页:`Docker Desktop for Mac`
|
||||
|
||||
安装完成后,先启动 Docker Desktop,等左上角状态变成运行中。
|
||||
|
||||
### 4.2 打开 Docker Socket 选项
|
||||
|
||||
按 OpenHands 官方本地安装说明,进入:
|
||||
|
||||
- `Docker Desktop`
|
||||
- `Settings`
|
||||
- `Advanced`
|
||||
- 勾选 `Allow the default Docker socket to be used`
|
||||
|
||||
这个开关是 OpenHands 本地运行最关键的前置条件之一。
|
||||
|
||||
### 4.3 验证 Docker 是否可用
|
||||
|
||||
在终端执行:
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
如果能正常返回容器列表或空列表,说明 Docker daemon 已经起来了。
|
||||
|
||||
### 4.4 启动 OpenHands
|
||||
|
||||
回到你的仓库目录后执行:
|
||||
|
||||
```bash
|
||||
openhands serve --mount-cwd
|
||||
```
|
||||
|
||||
如果一切正常,你会看到 OpenHands GUI Server 在本机启动。
|
||||
|
||||
## 5. 如果你暂时不想装 Docker
|
||||
|
||||
如果你只是想先体验界面,不急着本地执行仓库任务,可以先考虑 OpenHands 的 `web` 模式。这个模式是终端界面的浏览器版,不是完整 GUI Server,和 `serve` 不是一回事。
|
||||
|
||||
但如果你的目标是后面和 `memindadm` 做执行器集成,我还是建议装 Docker,后续链路会更稳。
|
||||
|
||||
## 6. 启动后如何确认正常
|
||||
|
||||
启动成功后,确认以下几点:
|
||||
|
||||
1. 浏览器能打开 OpenHands 的界面。
|
||||
2. 可以创建一次简单任务。
|
||||
3. 如果用了 `--mount-cwd`,任务可以看到当前仓库。
|
||||
4. Docker 不报权限或 socket 错误。
|
||||
|
||||
## 7. 用于后续 Goose 集成时的建议
|
||||
|
||||
为了后面接入 `memindadm`,建议你先准备好这些信息:
|
||||
|
||||
- OpenHands 的启动方式
|
||||
- 本地可访问地址
|
||||
- 是否需要挂载仓库目录
|
||||
- 是否允许命令执行
|
||||
- 是否要使用 GPU
|
||||
- 计划给 Goose 侧调用的入口方式
|
||||
|
||||
建议后续接入时优先走“策略中心路由到 OpenHands”,而不是让 Goose 直接接管 OpenHands 的所有行为。这样 `memindadm` 的审计和拦截链路会更清晰。
|
||||
|
||||
如果你后面已经决定把 `Goose / Aider / OpenHands` 的模型统一收口到 `memindadm`,那么 OpenHands 这边也不要再单独维护自己的模型配置,统一从后台读取即可。
|
||||
|
||||
## 8. 和现有 Goose 服务的关系
|
||||
|
||||
这一步是旁路安装,不会改动现有 Goose 服务。
|
||||
|
||||
后续集成时,建议保持下面的边界:
|
||||
|
||||
- 现有 Goose 服务继续照常运行
|
||||
- `memindadm` 只在旁路做策略判断、审计记录、路由建议
|
||||
- OpenHands 先作为独立执行器接入
|
||||
- 真正切流之前,先做只读审计和联调验证
|
||||
|
||||
## 9. 官方参考
|
||||
|
||||
- OpenHands 安装文档
|
||||
- OpenHands GUI Server
|
||||
- OpenHands Local Setup
|
||||
- OpenHands Docker Sandbox
|
||||
+69
-29
@@ -1,39 +1,48 @@
|
||||
# Plaza 由 105 入口转发到 Studio
|
||||
# Plaza 本机为主
|
||||
|
||||
`plaza.tkmind.cn` 现在解析到 105 服务器,105 只负责公网入口和反代,最终回源到 Studio 上的 Plaza 生产服务。
|
||||
105 服务器已停用。Plaza 与 Memind H5 均在本机 Mac 运行;公网 `plaza.tkmind.cn` 经 **Cloudflare Tunnel** 回源到本机。
|
||||
|
||||
## 架构
|
||||
|
||||
```text
|
||||
```
|
||||
浏览器 / 微信
|
||||
↓
|
||||
105 公网入口(plaza.tkmind.cn)
|
||||
↓ nginx / 反代
|
||||
Studio / 本机生产机
|
||||
Cloudflare(plaza.tkmind.cn)
|
||||
↓ Cloudflare Tunnel
|
||||
本机 Mac
|
||||
├─ :3001 Plaza Next.js(/plaza、/_next)
|
||||
│ └─ rewrites → :8081(/api、/auth、/u)
|
||||
└─ :8081 Memind Portal(server.mjs)
|
||||
```
|
||||
|
||||
Next.js 通过 `PLAZA_API_PROXY` 把 API 请求转发到 Portal,无需单独 nginx。105 侧只负责把 `https://plaza.tkmind.cn/plaza` 回源到 Studio 的 `:3001`。
|
||||
Next.js 通过 `PLAZA_API_PROXY` 把 API 请求转发到 Portal,无需单独 nginx。
|
||||
|
||||
## 运行前提
|
||||
## 首次配置(一次性)
|
||||
|
||||
- 105 上 nginx 配置正常,`/plaza` 和 `/_next` 代理到 Studio
|
||||
- Studio 上 Plaza 已完成 `next build`
|
||||
- Studio 上 Plaza 生产进程正在监听 `127.0.0.1:3001`
|
||||
- Studio 上 Portal 正在监听 `127.0.0.1:8081`
|
||||
```bash
|
||||
# 1. 本地 DNS(覆盖 Tailscale / Clash fake-ip)
|
||||
sudo pnpm setup:plaza-dns
|
||||
|
||||
# 2. 本地 HTTPS 证书(可选,供本机 https://plaza.tkmind.cn)
|
||||
sudo pnpm setup:plaza-local
|
||||
|
||||
# 3. 公网隧道(plaza.tkmind.cn → 本机 :3001)
|
||||
pnpm setup:plaza-tunnel
|
||||
```
|
||||
|
||||
## 日常启动
|
||||
|
||||
```bash
|
||||
# Studio 上启动 Plaza 生产模式
|
||||
# 本机正式模式(next build + start,供 Tunnel 回源,推荐公网)
|
||||
pnpm start:plaza
|
||||
|
||||
# 开发模式(热更新,仅本地调试)
|
||||
pnpm dev:plaza
|
||||
|
||||
# 全栈(MindSpace + Plaza + Ops)
|
||||
# 另开终端:本机 HTTPS 入口(可选,纯本地 HTTPS 时用)
|
||||
sudo pnpm dev:plaza-proxy
|
||||
|
||||
# 或全栈(MindSpace + Plaza + Ops)
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
@@ -42,30 +51,61 @@ pnpm dev
|
||||
| 场景 | 地址 |
|
||||
|------|------|
|
||||
| 本机直连 | http://127.0.0.1:3001/plaza |
|
||||
| 105 公网入口 | https://plaza.tkmind.cn/plaza |
|
||||
| 本机域名(需 proxy) | https://plaza.tkmind.cn/plaza |
|
||||
| 浏览器强制本地 | `pnpm open:plaza` |
|
||||
| 公网(需 dev:plaza + 隧道) | https://plaza.tkmind.cn/plaza |
|
||||
|
||||
## 常见故障
|
||||
### 浏览器提示「连接不是私密连接」?
|
||||
|
||||
### 访问 502
|
||||
本机曾用 `setup:plaza-dns` 把域名指到 `127.0.0.1`,HTTPS 走自签证书。若已配置 Cloudflare Tunnel,应 **去掉本地劫持**,走公网证书(仍回源本机):
|
||||
|
||||
优先按这个顺序检查:
|
||||
```bash
|
||||
sudo pnpm setup:plaza-dns:remove
|
||||
```
|
||||
|
||||
1. Studio 上 `http://127.0.0.1:3001/plaza` 是否返回 200
|
||||
2. Studio 上 Plaza 生产进程是否还在
|
||||
3. 105 上 nginx 是否已经 reload 到最新配置
|
||||
然后刷新 https://plaza.tkmind.cn/ 即可。
|
||||
|
||||
如果 Studio 的 `:3001` 没起来,105 再怎么转发也会 502。
|
||||
### 浏览器打不开?
|
||||
|
||||
### Studio 启动失败
|
||||
Chrome / Cursor 内置浏览器默认走 **Secure DNS**,会绕过 `/etc/hosts` 连到 Cloudflare(105 停服时显示 502)。
|
||||
|
||||
如果 `pnpm start:plaza` 一直退出,优先看 `~/Library/Logs/plaza-prod.log`。这次修复里我们遇到过两类问题:
|
||||
解决:
|
||||
|
||||
- `next/font/google` 在离线构建时会拉不到字体资源
|
||||
- 类型检查会因为事件批处理数组的推断过宽而失败
|
||||
1. `pnpm open:plaza`(推荐)
|
||||
2. 关闭浏览器「安全 DNS / Secure DNS」
|
||||
3. `pnpm check:plaza` 查看诊断
|
||||
|
||||
## 公网 Tunnel
|
||||
|
||||
隧道配置写在 `ollama-tkmind` 的 cloudflared config(默认 `~/Project/ollama/cloudflare/config.yml`)。
|
||||
|
||||
```bash
|
||||
pnpm setup:plaza-tunnel # 写入 ingress + DNS 路由 + 重启 cloudflared
|
||||
```
|
||||
|
||||
**前提:** `pnpm start:plaza`(或 `pnpm dev:plaza`)已在跑(:3001 可访问)。Mac 休眠或服务停掉时,外网会 502/503。
|
||||
|
||||
自定义:
|
||||
|
||||
```bash
|
||||
CLOUDFLARE_TUNNEL_CONFIG=/path/to/config.yml \
|
||||
CLOUDFLARE_TUNNEL_NAME=ollama-tkmind \
|
||||
PLAZA_TUNNEL_PORT=3001 \
|
||||
pnpm setup:plaza-tunnel
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
见 `.env.example` 中 Plaza 相关注释。常用:
|
||||
|
||||
| 变量 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `PLAZA_PORT` | 3001 | Plaza Next.js 端口 |
|
||||
| `H5_PORT` | 8081 | Portal / API 端口 |
|
||||
| `PLAZA_LOCAL_HOST` | plaza.tkmind.cn | 本地域名 |
|
||||
| `PLAZA_PUBLIC_BASE` | https://plaza.tkmind.cn | 公开 URL 前缀 |
|
||||
|
||||
## 已弃用
|
||||
|
||||
- 把 `plaza.tkmind.cn` 直接指到本机 `127.0.0.1` 的旧本地劫持流程
|
||||
- 直接把 `plaza.tkmind.cn` 说明成 Cloudflare Tunnel 直连本机的旧说法
|
||||
- “105 已停用、Plaza 全在本机且不经过 105”的旧文档描述
|
||||
- `pnpm deploy:105`、`pnpm deploy:plaza-105` — 105 不再作为主服务
|
||||
- 105 上 nginx / goose-h5 / goose-plaza-web — 已停服,公网不再回源 105
|
||||
|
||||
+39
-307
@@ -1,315 +1,47 @@
|
||||
# Memind 生产更新发布指南
|
||||
# Memind Portal 生产更新发布指南
|
||||
|
||||
> 本文描述如何把本地开发代码安全发布到 **g2.tkmind.cn** 生产环境。
|
||||
> 发布前请先阅读 [生产 / 测试 / 预览隔离规程](./service-isolation-runbook.md),避免误占生产端口或覆盖用户数据。
|
||||
> 2026-06-26 起,103 / Studio 正式禁止 `rsync` 发布。
|
||||
> Portal 生产必须是**无源码 runtime 模式**,唯一合法入口是 `bash scripts/release-portal-runtime-prod.sh`。
|
||||
|
||||
## 1. 架构与发布目标
|
||||
## 当前规则
|
||||
|
||||
用户访问 `https://g2.tkmind.cn/` 的流量路径:
|
||||
1. 构建只发生在本机 Mac:`node scripts/build-portal-runtime.mjs`。
|
||||
2. 103 只接收 `.runtime/portal/` 打出来的 artifact,不直接覆盖源码树。
|
||||
3. 103 在切换前必须做 `Memind` 全量备份,并单独备份持久目录。
|
||||
4. 103 **禁止** `npm install`、`npm run build`、在线改源码后继续运行。
|
||||
5. 切换后必须通过 Portal 健康检查;失败立即回滚。
|
||||
|
||||
```text
|
||||
阿里云解析(DNS)
|
||||
→ 105 服务器(公网入口 / 反代)
|
||||
→ 本地 Mac 1.6 机器(主服务)
|
||||
├─ 主流量 → 本地 Portal(127.0.0.1:8081)
|
||||
└─ 备用/灰度 → 105 Portal(经隧道 127.0.0.1:18080 → :8080)
|
||||
```
|
||||
|
||||
两台 Portal 都是 **无状态前端**,共用 Studio 上的 goosed 与 MindSpace 数据目录。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Dev[本地开发目录] -->|rsync_to_server.sh| Studio[Studio 生产机<br/>/Users/john/Project/Memind]
|
||||
Studio -->|sync-to-105.sh| N105[105 入口 / 反代]
|
||||
N105 -->|g2.tkmind.cn| G2[g2 H5 / Portal]
|
||||
N105 -->|plaza.tkmind.cn| Plaza[Plaza 前台]
|
||||
N105 -->|gadm.tkmind.cn| Gadm[memind_adm]
|
||||
G2 --> Portal[Portal :8081]
|
||||
Plaza --> PlazaSrv[Plaza :3001]
|
||||
Gadm --> AdmSrv[memind_adm :8082]
|
||||
```
|
||||
|
||||
| 角色 | 机器 | 代码目录 | 服务 | 重启方式 |
|
||||
|------|------|----------|------|----------|
|
||||
| 生产主 | 本地 Mac 1.6 机器 | `/Users/john/Project/Memind` | Portal `:8081`、Plaza `:3001` | `launchctl kickstart` |
|
||||
| 入口转发 | 105 服务器 | 入口转发配置 | 反代 `:80/:443` | 阿里云解析切换后生效 |
|
||||
| 灰度副 | 105(经 Tailscale `ssh105`) | `/root/tkmind_go/ui/h5` | `goose-h5` `:8080` | `systemctl restart goose-h5` |
|
||||
|
||||
更详细的流量与灰度比例说明见 [g2 负载均衡](./g2-load-balancing.md)。
|
||||
|
||||
## 2. 目录与环境对照
|
||||
|
||||
| 环境 | 典型目录 | 端口 | 用途 |
|
||||
|------|----------|------|------|
|
||||
| **生产(Studio)** | `/Users/john/Project/Memind` | `8081` / `3001` | 线上 g2 / plaza |
|
||||
| **开发预览** | `/Users/john/Project/test/Memind` 或本机副本 | `18081` / `13001` | 本地联调,禁止占 `8081` |
|
||||
| **105 副机** | `/root/tkmind_go/ui/h5` | `8080` | g2 灰度流量 |
|
||||
|
||||
**重要:** `rsync_to_server.sh` 会把**你执行命令时所在的本地目录**同步到 Studio 生产目录。发布前请确认当前目录里的代码就是你要上线的版本,而不是半成品或未验证的分支。
|
||||
|
||||
## 3. 发布入口(主流程)
|
||||
|
||||
**推荐唯一入口:** 项目根目录的 `rsync_to_server.sh`
|
||||
## 唯一入口
|
||||
|
||||
```bash
|
||||
cd /path/to/your/memind-repo # 开发完成、已自测的目录
|
||||
|
||||
# ① 预览(不修改任何远端)
|
||||
./rsync_to_server.sh --dry-run
|
||||
|
||||
# ② 正式发布(Studio + 105 全量)
|
||||
./rsync_to_server.sh
|
||||
bash scripts/release-portal-runtime-prod.sh --dry-run
|
||||
bash scripts/release-portal-runtime-prod.sh
|
||||
```
|
||||
|
||||
脚本会自动完成:
|
||||
|
||||
1. 本地 Pre-flight(关键文件、安全 patch、exclude 规则)
|
||||
2. Studio Pre-flight(SSH、`.env`、MindSpace、data 完整性)
|
||||
3. 交互确认(可用 `--yes` 跳过)
|
||||
4. rsync 代码 → Studio 生产目录
|
||||
5. rsync 后校验(`.env` 未变、MindSpace 未减少、patch 仍在)
|
||||
6. Studio 上 `npm install` + `npm run build`
|
||||
7. 重启本地 Mac 1.6 Portal → Plaza,并做健康检查
|
||||
8. 经 Studio 触发 `scripts/sync-to-105.sh`,同步并重启 105
|
||||
|
||||
## 4. 发布前检查清单
|
||||
|
||||
在 `./rsync_to_server.sh` 之前,逐项确认:
|
||||
|
||||
### 4.1 本地构建与测试
|
||||
|
||||
```bash
|
||||
pnpm install # 依赖有变更时
|
||||
pnpm run build # 前端改动必须能编过
|
||||
pnpm test # 建议跑;涉及核心逻辑时必跑
|
||||
```
|
||||
|
||||
| 改动类型 | 是否必须 build | 能否 `--skip-build` |
|
||||
|----------|----------------|---------------------|
|
||||
| 前端(`.tsx` / `.css` / `src/`) | **是** | 否 |
|
||||
| 仅后端(`.mjs`) | 否(但 build 无害) | 可以 |
|
||||
| 仅文档 / 脚本 | 否 | 可以 |
|
||||
|
||||
### 4.2 生产在线(只读)
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8081/api/status # 本地 Portal,期望 ok
|
||||
curl -s http://127.0.0.1:18080/api/status # 105 隧道,期望 ok
|
||||
```
|
||||
|
||||
105 联通必须走 Tailscale,不要用公网 IP:
|
||||
|
||||
```bash
|
||||
ssh ssh105 'echo tunnel-ok-105'
|
||||
```
|
||||
|
||||
### 4.3 安全约束(脚本会自动检查)
|
||||
|
||||
- `server.mjs` 必须含 `WORKSPACE_MAINTENANCE_ENABLED` patch(否则 105 重启会在 rclone 挂载上卡死)
|
||||
- `.env`、`MindSpace/`、`data/` 在 exclude 列表中,**不会被 rsync 覆盖**
|
||||
- rsync 使用 `--delete`:本地已删的文件会从 Studio 删掉(exclude 外的路径)
|
||||
|
||||
### 4.4 发布范围确认
|
||||
|
||||
- `rsync_to_server.sh` 同步的是**整个仓库**(除 exclude 外),不是单个文件
|
||||
- 工作区若有未完成的其它改动,会一并上线——发布前建议 commit 或整理干净
|
||||
- 涉及数据库迁移、批量清数据、支付回调测试等,**不要**直接在生产目录试跑,见 [隔离规程](./service-isolation-runbook.md)
|
||||
|
||||
## 5. 分步与保守发布
|
||||
|
||||
`rsync_to_server.sh` 支持的常用参数:
|
||||
|
||||
| 参数 | 作用 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| `--dry-run` | 只预览 diff,不改远端 | **每次正式发布前必做** |
|
||||
| `--only-100` | 只更新本地主机,不推 105 | 先让主流量生效,105 稍后 |
|
||||
| `--only-105` | 只触发本地主机→105 同步 | 主机已是最新,只补 105 |
|
||||
| `--skip-build` | 跳过远端 `npm run build` | 纯后端改动且确认 dist 无需更新 |
|
||||
| `--no-restart` | 只同步代码,不重启服务 | 分批发布;需自行重启 |
|
||||
| `--yes` / `-y` | 跳过交互确认 | 自动化或你已看过 dry-run |
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 只发 Studio(主流量 95%)
|
||||
./rsync_to_server.sh --only-100
|
||||
|
||||
# 纯 server.mjs 改动,跳过 build
|
||||
./rsync_to_server.sh --skip-build
|
||||
|
||||
# 先同步代码,稍后再重启
|
||||
./rsync_to_server.sh --no-restart
|
||||
# 之后在 Studio 上手动 kickstart,或再跑一遍带重启的同步
|
||||
```
|
||||
|
||||
## 6. 105 单独同步
|
||||
|
||||
若 Studio 代码已是最新,只需更新 105:
|
||||
|
||||
```bash
|
||||
# 在 Studio 生产目录执行
|
||||
cd /Users/john/Project/Memind
|
||||
bash scripts/sync-to-105.sh
|
||||
|
||||
# 或从本地只触发 105 链路
|
||||
./rsync_to_server.sh --only-105
|
||||
```
|
||||
|
||||
`sync-to-105.sh` 会:
|
||||
|
||||
1. rsync 代码(`--delete`)与 `dist/` 到 `root@ssh105:/root/tkmind_go/ui/h5`
|
||||
2. 远端 `npm install` + `npm run build`(可用 `SKIP_BUILD=1` 跳过)
|
||||
3. `systemctl restart goose-h5`
|
||||
4. 校验关键文件 md5 与 `:8080/api/status`
|
||||
|
||||
环境变量(可选):
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `H5_DEPLOY_HOST` | `root@ssh105` | 105 SSH 目标 |
|
||||
| `H5_REMOTE_DIR` | `/root/tkmind_go/ui/h5` | 105 代码路径 |
|
||||
| `H5_SYSTEMD_SERVICE` | `goose-h5` | systemd 服务名 |
|
||||
| `SKIP_BUILD` | `0` | `1` 跳过远端 build |
|
||||
| `NO_RESTART` | `0` | `1` 不重启服务 |
|
||||
|
||||
package.json 中的 `pnpm deploy:105` 等价于本地执行 `bash scripts/sync-to-105.sh`(需本机能 SSH 到 105,或已在 Studio 上)。
|
||||
|
||||
## 7. 发布过程与影响窗口
|
||||
|
||||
全量 `./rsync_to_server.sh` 典型耗时 **5~10 分钟**。
|
||||
|
||||
| 阶段 | 用户可见影响 |
|
||||
|------|----------------|
|
||||
| rsync + npm install + build | 无(旧进程仍在跑) |
|
||||
| 本地 Portal 重启 | **8081 短暂不可用**,约 10~40 秒;主流量可能短暂 502 |
|
||||
| 105 goose-h5 重启 | **灰度流量(约 5%)** 短暂不可用 |
|
||||
| Plaza 重启 | plaza.tkmind.cn 可能短暂不可用;与 g2 主聊天无关 |
|
||||
|
||||
入口反代会对不健康上游做健康检查,105 重启期间可能暂时从池中摘除。
|
||||
|
||||
## 8. 发布后验证
|
||||
|
||||
### 8.1 健康检查
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8081/api/status
|
||||
curl -s http://127.0.0.1:18080/api/status
|
||||
curl -s https://g2.tkmind.cn/api/status
|
||||
```
|
||||
|
||||
### 8.2 确认命中哪台上游
|
||||
|
||||
g2 响应头 `X-Memind-Upstream`:
|
||||
|
||||
- `127.0.0.1:8081` → 本地 Mac 1.6 机器
|
||||
- `127.0.0.1:18080` → 105
|
||||
|
||||
```bash
|
||||
curl -s -D - -o /dev/null https://g2.tkmind.cn/api/status | grep -i x-memind-upstream
|
||||
```
|
||||
|
||||
### 8.3 业务冒烟
|
||||
|
||||
按本次改动选手动验证,例如:
|
||||
|
||||
- 打开 g2 聊天页,测试新功能
|
||||
- 登录 / 微信 OAuth(若动到 auth)
|
||||
- MindSpace 页面读写(若动到 pages)
|
||||
- Plaza 列表(若动到 plaza)
|
||||
|
||||
### 8.4 日志
|
||||
|
||||
```bash
|
||||
# 本地 Portal
|
||||
tail -f ~/Library/Logs/memind-portal.log
|
||||
|
||||
# Studio Plaza
|
||||
tail -f ~/Library/Logs/plaza-prod.log
|
||||
|
||||
# 105(经 ssh105)
|
||||
ssh ssh105 'journalctl -u goose-h5 -n 50 --no-pager'
|
||||
```
|
||||
|
||||
## 9. 回滚思路
|
||||
|
||||
项目没有一键回滚脚本,常见做法:
|
||||
|
||||
1. **代码回滚:** 在本地 git 回到上一个 good commit,`./rsync_to_server.sh` 再发一版
|
||||
2. **仅本地主机:** 若 105 有问题,可临时把流量全部收回主机(见 [g2-load-balancing.md](./g2-load-balancing.md))
|
||||
3. **紧急恢复 Portal:** 若 8081 挂了,见 [隔离规程 · 事故恢复](./service-isolation-runbook.md#事故恢复最小步骤)
|
||||
|
||||
发布前建议打 tag 或记录当前 commit,便于回滚:
|
||||
|
||||
```bash
|
||||
git rev-parse HEAD
|
||||
git tag -a release-2026-06-19 -m "before voice UI deploy"
|
||||
```
|
||||
|
||||
## 10. 其它发布路径(非 g2 主站)
|
||||
|
||||
### 10.1 同步到局域网测试机
|
||||
|
||||
仅源码同步到 `192.168.1.9` 上的 test 目录,**不是生产**:
|
||||
|
||||
```bash
|
||||
bash scripts/deploy-to-test-host.sh --dry-run
|
||||
bash scripts/deploy-to-test-host.sh
|
||||
```
|
||||
|
||||
目标:`test-memind` / `test-memindadm` / `test-memindplaza`。同步后需按该机习惯手动重启服务。
|
||||
|
||||
### 10.2 Plaza 105 入口转发
|
||||
|
||||
```bash
|
||||
pnpm deploy:plaza-105
|
||||
```
|
||||
|
||||
`plaza.tkmind.cn` 一样是 `105 -> Studio` 的入口模式,只是它对应的是 Plaza 前台而不是管理后台。
|
||||
当前真实链路见 [Plaza 入口与发布](./plaza-local.md)。
|
||||
|
||||
### 10.3 gadm / memind_adm
|
||||
|
||||
`gadm.tkmind.cn` 的生产入口和 Plaza 相同,也是 `105 -> Studio`。
|
||||
更完整的操作手册见 [gadm 入口与发布](./gadm-local.md)。
|
||||
|
||||
### 10.4 已废弃 / 不可用
|
||||
|
||||
| 命令 | 状态 |
|
||||
|------|------|
|
||||
| `pnpm deploy:prod` | 指向 `../../deploy/deploy-h5-prod.sh`,当前仓库旁路不存在,**勿用** |
|
||||
|
||||
## 11. 推荐发布 SOP(标准作业)
|
||||
|
||||
适合大多数功能迭代的固定步骤:
|
||||
|
||||
```text
|
||||
1. 在测试端口完成开发与自测(18081,勿占 8081)
|
||||
2. pnpm run build && pnpm test
|
||||
3. ./rsync_to_server.sh --dry-run ← 看清将要同步什么
|
||||
4. 确认无多余改动、无数据库破坏性操作
|
||||
5. ./rsync_to_server.sh ← 全量 Studio + 105
|
||||
6. curl 健康检查 + 浏览器冒烟
|
||||
7. 观察 5~10 分钟日志,确认无 ERROR 尖峰
|
||||
```
|
||||
|
||||
**保守版(先主后副):**
|
||||
|
||||
```text
|
||||
1~4 同上
|
||||
5. ./rsync_to_server.sh --only-100
|
||||
6. 冒烟通过后
|
||||
7. ./rsync_to_server.sh --only-105
|
||||
```
|
||||
|
||||
## 12. 相关文档
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| [service-isolation-runbook.md](./service-isolation-runbook.md) | 生产 / 测试端口隔离、禁止事项、事故恢复 |
|
||||
| [g2-load-balancing.md](./g2-load-balancing.md) | g2 权重、105 隧道、灰度比例调整 |
|
||||
| [local-dev.md](./local-dev.md) | 本地开发端口与 preview |
|
||||
| [plaza-local.md](./plaza-local.md) | Plaza 部署与 Tunnel |
|
||||
|
||||
---
|
||||
|
||||
**维护说明:** 若部署脚本路径、主机名或 systemd 服务名变更,请同步更新本文与 `rsync_to_server.sh` 头部注释。
|
||||
## 产物发布流程
|
||||
|
||||
1. 本地 commit 当前改动。
|
||||
2. 本机构建 runtime:`node scripts/build-portal-runtime.mjs`(发布脚本默认会自动执行)。
|
||||
3. 校验 artifact 至少包含:`server.mjs`、`mindspace-sandbox-mcp.mjs`、`dist/`、`scripts/run-memind-portal-prod.sh`。
|
||||
4. 本地生成 `memind-portal-runtime-<release-id>.tar.gz` 和发布清单。
|
||||
5. 通过 `scp` 上传到 103 的 `incoming/memind-portal-runtime/`。
|
||||
6. 103 备份当前 `/Users/john/Project/Memind` 全目录 + 持久目录。
|
||||
7. 停止旧 Portal,解包 runtime artifact 到新的 live 目录,继承 `.env` / `MindSpace/` / `data/` / `users/` / `.tailscale/` / `public/plaza-covers/` / `logs/`。
|
||||
8. 更新 LaunchAgent 指向 `${APP_DIR}/scripts/run-memind-portal-prod.sh`。
|
||||
9. 启动 Portal,检查 `127.0.0.1:8081/api/status` 为 200。
|
||||
10. 旧源码 live 目录移入 `archives/`,线上不再保留可运行源码树。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 禁止 `rsync_to_server.sh`
|
||||
- 禁止 `bash scripts/release-prod.sh`(源码包发布已停用)
|
||||
- 禁止手工拖文件覆盖 103
|
||||
- 禁止在 103 直接改源码后继续跑
|
||||
- 禁止跳过备份和健康检查
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Portal 无源码迁移说明](no-source-portal-migration.md)
|
||||
- [开发环境规则](../DEVELOPMENT_RELEASE_RULES.md)
|
||||
- [测试发布规则](../TEST_RELEASE_RULES.md)
|
||||
- [生产发布规则](../PRODUCTION_RELEASE_RULES.md)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
> **生产环境警示:当前目录 `/Users/john/Project/Memind` 为生产目录与生产环境,禁止重启服务,所有操作必须谨慎并优先避免影响在线流量。**
|
||||
> **隔离说明:这份文档只讲生产 / 测试 / 预览边界和事故恢复,不是本地联调手册。要做本机开发,请看 [docs/local-dev.md](./local-dev.md)。**
|
||||
|
||||
# 生产 / 测试 / 预览隔离规程
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
| 环境 | 目录 | 用途 | 端口 |
|
||||
|------|------|------|------|
|
||||
| 生产 | `/Users/john/Project/Memind` | `g2.tkmind.cn` 当前在线服务(阿里云解析 → 105 → 本地 Mac 1.6) | `8081` |
|
||||
| 生产 Plaza | `/Users/john/Project/Memind` + Plaza | `plaza.tkmind.cn` 当前在线服务(105 反代到 Studio) | `3001` |
|
||||
| 生产 | `/Users/john/Project/Memind` | `g2.tkmind.cn` 当前在线服务(阿里云解析 → 105 → 本机 Mac) | `8081` |
|
||||
| 生产 Plaza | `/Users/john/Project/Memind` + Plaza | `plaza.tkmind.cn` 当前在线服务 | `3001` |
|
||||
| 生产入口 | `/Users/john/Project/Memind/scripts/g2-lb.Caddyfile` | 105 转发入口 / 反代配置 | `8090` |
|
||||
| 测试 Portal | `/Users/john/Project/test/Memind` | 开发预览 API / Portal | `18081` |
|
||||
| 测试 Vite | `/Users/john/Project/test/Memind` | 开发预览前端 | `15173` |
|
||||
| 测试 Admin | `/Users/john/Project/test/Memind` | 开发预览后台 | `18082` |
|
||||
| 测试 Plaza | `/Users/john/Project/test/Memind` | 开发预览 Plaza | `13001` |
|
||||
| 测试 Ops | `/Users/john/Project/test/Memind` | 开发预览 Ops | `13002` |
|
||||
| 测试 Portal | `/Users/john/PycharmProjects/test/test-memind` | 开发预览 API / Portal | `18081` |
|
||||
| 测试 Vite | `/Users/john/PycharmProjects/test/test-memind` | 开发预览前端 | `15173` |
|
||||
| 测试 Admin | `/Users/john/PycharmProjects/test/test-memind` | 开发预览后台 | `18082` |
|
||||
| 测试 Plaza | `/Users/john/PycharmProjects/test/test-memind` | 开发预览 Plaza | `13001` |
|
||||
| 测试 Ops | `/Users/john/PycharmProjects/test/test-memind` | 开发预览 Ops | `13002` |
|
||||
|
||||
硬规则:
|
||||
|
||||
@@ -63,8 +63,8 @@ g2.tkmind.cn 生产请求
|
||||
| 项目目录 | 记忆含义 |
|
||||
|----------|----------|
|
||||
| `/Users/john/Project/Memind` | 生产项目的开发记忆 |
|
||||
| `/Users/john/Project/test/Memind` | 测试项目的开发记忆 |
|
||||
| `/Users/john/Project/test/test_tkmind_go` | 测试 Goose 的开发记忆 |
|
||||
| `/Users/john/PycharmProjects/test/test-memind` | 测试项目的开发记忆 |
|
||||
| 测试 Goose 项目目录(按本机实际路径填写) | 测试 Goose 的开发记忆 |
|
||||
|
||||
硬规则:
|
||||
|
||||
@@ -109,7 +109,7 @@ scripts/install-prod-services.sh
|
||||
优先在测试目录进行:
|
||||
|
||||
```bash
|
||||
cd /Users/john/Project/test/Memind
|
||||
cd /Users/john/PycharmProjects/test/test-memind
|
||||
```
|
||||
|
||||
启动前先确认生产还在:
|
||||
@@ -145,7 +145,7 @@ pnpm dev
|
||||
如果只改前端样式,优先只启动 Vite:
|
||||
|
||||
```bash
|
||||
cd /Users/john/Project/test/Memind
|
||||
cd /Users/john/PycharmProjects/test/test-memind
|
||||
VITE_PORT=15173 \
|
||||
H5_PUBLIC_BASE_URL=http://127.0.0.1:15173 \
|
||||
VITE_MINDSPACE_BASE=http://127.0.0.1:15173 \
|
||||
@@ -171,6 +171,68 @@ pnpm dev:vite -- --host 127.0.0.1 --port 15173
|
||||
|
||||
测试 H5 如果要连测试 Goose,必须在测试目录 `.env` 中显式设置对应地址,不能指向生产 Goose。
|
||||
|
||||
## goosed extension 堆积与文件句柄告警
|
||||
|
||||
如果 H5 / MindSpace 侧出现下面这类错误:
|
||||
|
||||
```text
|
||||
会话策略同步失败:{"message":"Failed to add extension: IO error: Too many open files (os error 24)"}
|
||||
```
|
||||
|
||||
先不要直接重启生产服务。这个报错在本机更常见的含义是:
|
||||
|
||||
- `goosed agent` 下面堆积了过多旧的 extension 子进程。
|
||||
- macOS `launchctl limit maxfiles` 软上限偏低时,`/agent/add_extension` 会先撞到文件句柄上限。
|
||||
- 如果恢复会话时无条件重启 agent,会把旧子进程堆得更快。
|
||||
|
||||
先做只读确认:
|
||||
|
||||
```bash
|
||||
launchctl limit maxfiles
|
||||
lsof -nP -iTCP -sTCP:LISTEN | rg '18006|18007|8081|3001|8090'
|
||||
for pid in 51613 51657; do
|
||||
echo "PID $pid $(ps -p $pid -o command=)"
|
||||
lsof -n -p $pid 2>/dev/null | wc -l
|
||||
done
|
||||
ps -axo pid,ppid,etime,command | rg 'mindspace-sandbox-mcp|mcp-server-fetch|goosed agent'
|
||||
```
|
||||
|
||||
判断口径:
|
||||
|
||||
- `8081`、`3001`、`8090` 正常监听,说明主站未必有故障。
|
||||
- 如果某个 `goosed agent` 的 FD 数已经逼近或超过 `256`,优先怀疑 extension 子进程堆积。
|
||||
- 如果同一个 `MindSpace/<id>` 在同一个 `goosed` 父进程下出现多个 `mindspace-sandbox-mcp.mjs`,通常只应保留最新一个。
|
||||
|
||||
安全处理顺序:
|
||||
|
||||
1. 先确认主站端口 `8081`、`3001`、`8090` 正常,不要把 route 问题误判成全站宕机。
|
||||
2. 不先杀 `goosed agent` 主进程,也不要碰 `8081` 上的 `server.mjs`。
|
||||
3. 优先只清理同父进程、同 `MindSpace/<id>` 下重复堆积的旧 `mindspace-sandbox-mcp.mjs` 子进程,以及重复的旧 `mcp-server-fetch` 子进程。
|
||||
4. 清理后立刻复查 `18006` / `18007` 的 `/status` 和 `goosed` FD 数,确认 agent 仍存活。
|
||||
|
||||
这次线上排查的经验值:
|
||||
|
||||
- 两个 `goosed agent` 的 FD 数曾达到 `263` / `209`,而 `launchctl limit maxfiles` 软上限是 `256`。
|
||||
- 仅清理重复 extension 子进程后,FD 数降到 `96` / `77`,`18006` 和 `18007` 的 `/status` 仍为 `ok`。
|
||||
- 代码层已改为仅在会话策略真正变化时才触发 agent 重启,减少恢复会话时继续堆积 extension 子进程的概率。
|
||||
|
||||
自动巡检:
|
||||
|
||||
- `scripts/monitor-goosed-fds.mjs` 每次检查 `goosed agent` FD 数和 `mindspace-sandbox-mcp.mjs` 子进程数。
|
||||
- `scripts/install-goosed-monitor.sh` 会安装 `~/Library/LaunchAgents/cn.tkmind.goosed-monitor.plist`,默认每 60 秒运行一次。
|
||||
- 默认策略只清理:
|
||||
- orphan 的 sandbox MCP 子进程;
|
||||
- 同一父进程、同一 `MindSpace/<id>` 下超过 2 个的重复 sandbox MCP;
|
||||
- 总 sandbox MCP 数超过 80 时最旧的一批。
|
||||
- 默认不按存活时间清理单个老进程,避免误伤长会话。
|
||||
- goosed FD 超过 `GOOSED_FD_WARN=180` 只记录 warning;超过 `GOOSED_FD_RESTART=3200` 才滚动重启对应 goosed。
|
||||
|
||||
硬规则:
|
||||
|
||||
- 不要因为 `Too many open files` 先去重启 `8081` 生产 Portal。
|
||||
- 不要在未确认影响窗口前直接重启 `goosed agent` 主进程。
|
||||
- 优先清理“明显重复”的 extension 子进程,而不是清空所有 agent 子进程。
|
||||
|
||||
## 数据库口径
|
||||
|
||||
短期如果沿用同一个 RDS 库,只允许做 UI 和非破坏性流程预览。
|
||||
@@ -190,7 +252,7 @@ pnpm dev:vite -- --host 127.0.0.1 --port 15173
|
||||
|
||||
最小检查:
|
||||
|
||||
1. 在测试端口(如 `18081`)完成开发与预览,不要在生产目录跑 `pnpm dev`。
|
||||
1. 在测试端口(如 `18081`)完成开发与预览;本机联调目录请看 [docs/local-dev.md](./local-dev.md),不要把这份规程当成开发启动手册。
|
||||
2. `pnpm run build` + `pnpm test`。
|
||||
3. `curl -s http://127.0.0.1:8081/api/status` 确认生产仍在线。
|
||||
4. `./rsync_to_server.sh --dry-run` 预览 diff,确认后再执行正式发布。
|
||||
@@ -220,10 +282,3 @@ curl -s http://127.0.0.1:8081/api/status
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8090/api/status
|
||||
```
|
||||
|
||||
如果要同时确认 Plaza 和 `gadm`,直接检查:
|
||||
|
||||
```bash
|
||||
curl -s https://plaza.tkmind.cn/plaza
|
||||
curl -s https://gadm.tkmind.cn/healthz
|
||||
```
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
# Studio Goose 权限与 Session 排障 Runbook
|
||||
|
||||
> 目的:以后再遇到“库里有权限,但 Agent 在对话里说自己没有某能力”的问题,直接按这份 runbook 查,不要从头摸。
|
||||
|
||||
> 2026-06-22 已确认的稳定事实:
|
||||
|
||||
- Studio 上 Goose 是双实例:`18006` 主、`18007` 备用/第二实例。
|
||||
- 105 不跑 Goose;105 只是无状态 H5 前端,代理回 Studio。
|
||||
- Studio 可直接 SSH:`ssh john@58.38.22.103`。不要为了查 Studio 再绕 105。
|
||||
- H5 会话权限不是只看数据库,还要看会话启动时实际下发给 Goose 的 `extension_overrides`。
|
||||
|
||||
## 这次案例结论
|
||||
|
||||
用户:
|
||||
|
||||
- `username`: `wx_ul610et8`
|
||||
- `display_name`: `唐`
|
||||
- `user_id`: `a70ff537-8908-486e-9b6c-042e07cc25db`
|
||||
|
||||
线上生产结论:
|
||||
|
||||
- `h5_capability_grants` 里该用户有 `aider=1`
|
||||
- 角色默认 `role:user` 也有 `aider=1`
|
||||
- `h5_user_policies` 里该用户没有单独禁用项
|
||||
- 角色策略是:
|
||||
- `workspace_access=readwrite`
|
||||
- `network_egress=allow`
|
||||
- `goose_mode=auto`
|
||||
- `api_lockdown=true`
|
||||
|
||||
按当前代码实时计算 `getAgentSessionPolicy(userId)` 的结果:
|
||||
|
||||
- `capabilities.aider = true`
|
||||
- `developer` 扩展应带 `write/edit/shell/tree/read_image`
|
||||
- `extensionOverrides` 里应出现 `aider`
|
||||
|
||||
因此:
|
||||
|
||||
- 如果对话里 Agent 说“没有 `aider` / 没有 `write_file` / 没有 `edit_file`”,优先怀疑是 **旧会话扩展未同步**、**Goose 实例漂移** 或 **子会话能力不一致**,不是先怀疑 DB 权限没开。
|
||||
- 注意:`aider` grant 为 true 只表示会话可挂 `aider` 平台扩展;Goose 的 `aider` 执行器仍会按任务/目录做运行时保护。MindSpace 用户工作区默认禁止 `aider` 直接执行,除非显式打开 `GOOSE_AIDER_ALLOW_MINDSPACE=1`。
|
||||
|
||||
## 2026-06-22 实际修复记录
|
||||
|
||||
用户贴出的异常话术对应 session:
|
||||
|
||||
- `agent_session_id`: `20260620_45`
|
||||
- `title`: `西湖跑步喝茶计划`
|
||||
- `goosed_node`: `0`
|
||||
- 实例:Studio 主 Goose `18006`
|
||||
|
||||
异常表现:
|
||||
|
||||
- Goose session 里有 `aider` 扩展
|
||||
- `developer.available_tools` 只有 `read_image`
|
||||
- 缺少 `sandbox-fs`
|
||||
- 因此 Agent 看不到 `write_file` / `edit_file`
|
||||
|
||||
已执行修复:
|
||||
|
||||
- 用生产代码的 `getAgentSessionPolicy(userId)` 重新计算该用户应有能力
|
||||
- 对 `20260620_45` 执行 `reconcileAgentSession(...)`
|
||||
- 修复后 `20260620_45` 已挂上 `sandbox-fs`
|
||||
- `sandbox-fs.available_tools` 已包含:
|
||||
- `read_file`
|
||||
- `write_file`
|
||||
- `edit_file`
|
||||
- `create_dir`
|
||||
- `list_dir`
|
||||
- `private_data_info`
|
||||
- `private_data_schema`
|
||||
- `private_data_query`
|
||||
- `private_data_execute`
|
||||
|
||||
这说明原问题已经定位并修复到具体旧会话:不是用户 DB 权限缺失,而是旧 Goose session 的扩展集没有包含当前策略要求的 `sandbox-fs`。
|
||||
|
||||
同时确认:
|
||||
|
||||
- `aider__code` 对 MindSpace 页面类任务返回 `Selected engine: blocked` 是运行时保护策略,不是这个用户没有 `aider` grant。
|
||||
- 相关保护在两处存在:
|
||||
- `/Users/john/Project/tkmind_go/crates/goose/src/agents/platform_extensions/aider.rs`
|
||||
- `/Users/john/Project/tkmind_go/deploy/coding_router.sh`
|
||||
- 对 MindSpace 页面/HTML/游戏生成任务,应使用 `sandbox-fs` 的 `write_file/edit_file` 或发布/App 工具,不应强制走 `aider`。
|
||||
|
||||
## 2026-06-22 深入排查:其他用户影响面
|
||||
|
||||
排查口径:
|
||||
|
||||
- 不要只看 Goose SQLite 的 `sessions.extension_data`,它是历史快照/扩展记忆结构,实时状态以 Goose API 为准。
|
||||
- 正确实时检查方式:按 `h5_user_sessions.goosed_node` 路由到 `18006` 或 `18007`,调用 `/sessions/{id}/extensions`。
|
||||
|
||||
最近 120 条 active 用户 session 的实时抽样结果:
|
||||
|
||||
- 可访问 session:120/120
|
||||
- 缺 `sandbox-fs`:24/120
|
||||
- node 0:89 条,缺 21 条
|
||||
- node 1:31 条,缺 3 条
|
||||
- 缺失集中在 2026-06-18 到 2026-06-20 的旧会话;2026-06-21 到 2026-06-22 新会话抽样未见缺失。
|
||||
|
||||
受影响样本用户:
|
||||
|
||||
- `wx_ul610et8 / 唐`:3 条旧会话仍缺 `sandbox-fs`
|
||||
- `wx_mk4zzaps / Mark 刘名章`:1 条旧会话缺 `sandbox-fs`
|
||||
- `wx_5dljrg1a / 123`:1 条旧会话缺 `sandbox-fs`
|
||||
- `john / John`:若干旧测试/管理会话缺 `sandbox-fs`
|
||||
- `admin / 管理员`:若干管理会话缺 `sandbox-fs`
|
||||
|
||||
结论:
|
||||
|
||||
- 这不是“当前双 Goose 负载随机丢能力”的现象。
|
||||
- 更像是 `sandbox-fs` 上线/策略迁移前后创建的旧 session 没有自动补齐扩展。
|
||||
- 双 Goose 会放大排查复杂度,因为每个 session 固定在自己的 `goosed_node`,必须去对应节点查实时 extensions;但只要 `start/resume/reply` 都走策略同步,就不会因为双 Goose 本身产生跨节点同步问题。
|
||||
|
||||
代码兜底:
|
||||
|
||||
- `/agent/start` 已同步策略。
|
||||
- `/agent/resume` 已同步策略。
|
||||
- 2026-06-22 新增本地修复:`/sessions/:id/reply` 前也调用 `reconcileSessionPolicyForUser(...)`。
|
||||
- 这样旧会话即使没有显式经过前端 `resumeSession()`,在下一次发消息前也会先补齐 `sandbox-fs` 等当前策略要求的扩展。
|
||||
|
||||
验证:
|
||||
|
||||
- `node --check server.mjs` 通过
|
||||
- `node --check tkmind-proxy.mjs` 通过
|
||||
- `node --test session-reconcile.test.mjs` 通过,6/6
|
||||
- `node --test policies.test.mjs capabilities.test.mjs user-memory-profile.test.mjs` 通过,38/38
|
||||
- 全量 `npm test -- session-reconcile.test.mjs` 会被项目脚本展开成全量测试,目前失败在既有 `llm-providers.test.mjs` 断言:实际返回 `Aider 未启用模型绑定`,不是本次 session policy sync 改动引入。
|
||||
|
||||
## 代码链路
|
||||
|
||||
关键代码:
|
||||
|
||||
- [user-auth.mjs](../user-auth.mjs)
|
||||
- `resolveUserCapabilities()`
|
||||
- `resolveUserPolicies()`
|
||||
- `getAgentSessionPolicy()`
|
||||
- [policies.mjs](../policies.mjs)
|
||||
- `applyPoliciesToCapabilities()`
|
||||
- [capabilities.mjs](../capabilities.mjs)
|
||||
- `buildAgentExtensionPolicy()`
|
||||
- [tkmind-proxy.mjs](../tkmind-proxy.mjs)
|
||||
- `POST /agent/start`
|
||||
- `POST /agent/resume`
|
||||
- `resolveTarget(sessionId)`
|
||||
|
||||
关键判断:
|
||||
|
||||
- `workspace_access=readonly` 会强制关闭:
|
||||
- `shell`
|
||||
- `filesystem`
|
||||
- `static_publish`
|
||||
- `aider`
|
||||
- `POST /agent/start` 会实时计算 `getAgentSessionPolicy()` 并下发 `extension_overrides`
|
||||
- `POST /agent/resume` 恢复旧会话时,必须结合 session 所在 Goose 实例继续看,不要只看数据库
|
||||
|
||||
## 架构事实
|
||||
|
||||
Studio H5 侧:
|
||||
|
||||
- 运行中 Portal 的 `TKMIND_API_TARGET` 指向主 Goose:`https://127.0.0.1:18006`
|
||||
- 运行中 Portal 的 `TKMIND_API_TARGET_1` 指向第二 Goose:`https://127.0.0.1:18007`
|
||||
- `server.mjs` 会构造:
|
||||
- `API_TARGETS = [TKMIND_API_TARGET, TKMIND_API_TARGET_1]`
|
||||
- 105 或外部机器可以通过 Tailscale/内网目标代理到 Studio,但排查 Studio 时优先直接 SSH 到 Studio。
|
||||
|
||||
Session 路由:
|
||||
|
||||
- `h5_user_sessions.goosed_node`
|
||||
- `0` 表示主 Goose
|
||||
- `1` 表示第二 Goose
|
||||
- `tkmind-proxy.resolveTarget(sessionId)` 会按 `goosed_node` 把 session 路由回对应 Goose
|
||||
|
||||
## 标准排查顺序
|
||||
|
||||
### 1. 先查用户真实权限,不要先猜
|
||||
|
||||
查 `h5_capability_grants`:
|
||||
|
||||
- 用户级覆盖
|
||||
- `role:user` 默认值
|
||||
|
||||
查 `h5_user_policies`:
|
||||
|
||||
- 用户级覆盖
|
||||
- `role:user` 默认值
|
||||
|
||||
重点看:
|
||||
|
||||
- `aider`
|
||||
- `filesystem`
|
||||
- `shell`
|
||||
- `code_browse`
|
||||
- `static_publish`
|
||||
- `workspace_access`
|
||||
|
||||
### 2. 再让线上代码实时算一次 session policy
|
||||
|
||||
不要只看表。
|
||||
|
||||
必须直接调用:
|
||||
|
||||
- `getAgentSessionPolicy(userId)`
|
||||
|
||||
重点看结果里的:
|
||||
|
||||
- `capabilities`
|
||||
- `policies`
|
||||
- `extensionOverrides`
|
||||
- 是否真的有 `aider`
|
||||
- `developer.available_tools` 是否真的包含 `write/edit`
|
||||
|
||||
### 3. 再看 session 落到哪个 Goose 实例
|
||||
|
||||
查:
|
||||
|
||||
- `h5_user_sessions.agent_session_id`
|
||||
- `h5_user_sessions.goosed_node`
|
||||
|
||||
如果同一用户的不同 session 分布在 `0` 和 `1` 两个节点:
|
||||
|
||||
- 说明必须分别检查 `18006` 和 `18007`
|
||||
- 不能只看一个 Goose 的状态就下结论
|
||||
|
||||
### 4. 如果用户说“这条对话里没有能力”
|
||||
|
||||
优先排查:
|
||||
|
||||
1. 这条对话是否是旧 session
|
||||
2. 这条对话是否落在第二 Goose
|
||||
3. 两个 Goose 的扩展配置是否漂移
|
||||
4. 是否进入了能力被收窄的子会话
|
||||
|
||||
## 这次已确认的反常点
|
||||
|
||||
- 线上 DB 与 `getAgentSessionPolicy()` 都显示:`wx_ul610et8` 应有 `aider` 和 `write/edit`
|
||||
- 但用户贴出的某条对话话术显示:Agent 自述没有 `write_file / edit_file`
|
||||
- 因为 Studio 是双 Goose,不是单 Goose,所以这类现象优先按“实例间配置漂移”处理
|
||||
|
||||
## 下次直接复用的命令清单
|
||||
|
||||
### 查 105 H5 当前指向的 Goose / RDS
|
||||
|
||||
优先查 Studio:
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=8 john@58.38.22.103 \
|
||||
"cd /Users/john/Project/Memind && ps eww -p \$(pgrep -f 'node .*server.mjs' | head -1) | tr ' ' '\n' | grep '^TKMIND_API_TARGET'"
|
||||
```
|
||||
|
||||
如果确实要查 105:
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519 root@120.26.184.105 \
|
||||
"sed -n '1,80p' /root/tkmind_go/ui/h5/.env"
|
||||
```
|
||||
|
||||
### 查生产用户权限
|
||||
|
||||
```bash
|
||||
ssh john@58.38.22.103
|
||||
cd /Users/john/Project/Memind
|
||||
|
||||
node -e 'process.loadEnvFile(".env"); const mysql=require("mysql2/promise"); (async()=>{ const pool=mysql.createPool(process.env.DATABASE_URL); const userId="a70ff537-8908-486e-9b6c-042e07cc25db"; const [userCaps]=await pool.query("SELECT capability_key, allowed FROM h5_capability_grants WHERE subject_type=\"user\" AND subject_id=? ORDER BY capability_key",[userId]); const [roleCaps]=await pool.query("SELECT capability_key, allowed FROM h5_capability_grants WHERE subject_type=\"role\" AND subject_id=\"user\" ORDER BY capability_key"); const [userPolicies]=await pool.query("SELECT policy_key, policy_value FROM h5_user_policies WHERE subject_type=\"user\" AND subject_id=? ORDER BY policy_key",[userId]); const [rolePolicies]=await pool.query("SELECT policy_key, policy_value FROM h5_user_policies WHERE subject_type=\"role\" AND subject_id=\"user\" ORDER BY policy_key"); console.log(JSON.stringify({userCaps,roleCaps,userPolicies,rolePolicies},null,2)); await pool.end(); })();'
|
||||
```
|
||||
|
||||
### 让线上代码直接算最终 session policy
|
||||
|
||||
```bash
|
||||
ssh john@58.38.22.103
|
||||
cd /Users/john/Project/Memind
|
||||
|
||||
node --input-type=module -e 'import process from "node:process"; process.loadEnvFile(".env"); const { createDbPool } = await import("./db.mjs"); const { createUserAuth } = await import("./user-auth.mjs"); const pool = createDbPool(); const userAuth = createUserAuth(pool, { h5Root: process.cwd() }); const policy = await userAuth.getAgentSessionPolicy("a70ff537-8908-486e-9b6c-042e07cc25db"); console.log(JSON.stringify(policy, null, 2)); await pool.end();'
|
||||
```
|
||||
|
||||
### 查用户最近 session 及节点分布
|
||||
|
||||
```bash
|
||||
ssh john@58.38.22.103
|
||||
cd /Users/john/Project/Memind
|
||||
|
||||
node -e 'process.loadEnvFile(".env"); const mysql=require("mysql2/promise"); (async()=>{ const pool=mysql.createPool(process.env.DATABASE_URL); const [rows]=await pool.query("SELECT agent_session_id, user_id, goosed_node, created_at FROM h5_user_sessions WHERE user_id = ? ORDER BY created_at DESC LIMIT 20", ["a70ff537-8908-486e-9b6c-042e07cc25db"]); console.log(JSON.stringify(rows,null,2)); await pool.end(); })();'
|
||||
```
|
||||
|
||||
### 查某条 Goose session 实际扩展
|
||||
|
||||
先用 `h5_user_sessions.goosed_node` 判断端口:
|
||||
|
||||
- `0` -> `18006`
|
||||
- `1` -> `18007`
|
||||
|
||||
然后查扩展:
|
||||
|
||||
```bash
|
||||
ssh john@58.38.22.103 \
|
||||
'curl -sk -H "X-Secret-Key: local-dev-secret" https://127.0.0.1:18006/sessions/20260620_45/extensions | python3 -m json.tool'
|
||||
```
|
||||
|
||||
## 必记事项
|
||||
|
||||
- 以后再看到“Agent 说自己没有 `aider` / `write_file` / `edit_file`”,不要直接重新从数据库查起。
|
||||
- 先看:
|
||||
- 这条 session 在哪个 `goosed_node`
|
||||
- 那个 Goose 实例是不是 `18006` 还是 `18007`
|
||||
- 那个实例实际拿到的 `extension_overrides`
|
||||
- 如果文档还没补全第二 Goose 的运行环境注入方式,优先继续补这里,不要让同样的排查再来一遍。
|
||||
Reference in New Issue
Block a user