# Trade Message Center ## 本地开发要求 - Node.js `>=22.22.2 <23` - pnpm `11.7.0` - 可连接的 PostgreSQL 数据库 - Chrome 或 Chromium(用于加载和调试扩展) 安装依赖: ```bash pnpm install ``` ## GitNexus 代码智能 GitNexus 会把代码解析为符号、调用关系和执行流程图,适合在改动前定位实现、评估影响范围,以及在提交前核对实际波及的模块。索引保存在本机的 `.gitnexus/`,不会提交到 Git。 ### 安装与首次建索引 本仓库已经生成过 `.gitnexus/run.cjs`;日常使用不需要全局安装。新 clone 没有该文件时,在仓库根目录执行一次: ```bash npx gitnexus@latest analyze ``` 若本机的 npm 11 在安装原生依赖时失败,改用 pnpm: ```bash pnpm --allow-build=@ladybugdb/core --allow-build=gitnexus --allow-build=tree-sitter \ dlx gitnexus@latest analyze ``` 也可以安装全局命令,之后上述本地 runner 会优先使用它: ```bash npm install -g gitnexus ``` ### 索引维护 在进行较大代码变更后、GitNexus 报告索引过期时,或需要重新生成 AI 指引文件时,执行: ```bash node .gitnexus/run.cjs analyze ``` 常用维护命令: ```bash node .gitnexus/run.cjs status # 查看索引状态、版本和统计信息 node .gitnexus/run.cjs list # 列出本机已索引仓库 node .gitnexus/run.cjs clean # 删除当前仓库的本地索引;执行前会要求确认 ``` `clean` 会移除 `.gitnexus/`,只在索引损坏或确定不再需要时使用;随后可用 `analyze` 重新生成。 ### 在 AI 编程会话中使用 已配置 GitNexus MCP 的客户端(如本项目的 Codex/Claude 环境)可直接读取 `gitnexus://repo/trade-message-center/context` 确认索引是否可用。推荐按任务类型选择工具: | 目标 | 工具 | 示例 | | -------------------------- | ---------------- | --------------------------------------------------------------- | | 理解功能或调用链 | `query` | `query({ query: "OneTalk 授权" })` | | 查看某个符号的完整上下文 | `context` | `context({ name: "authorizeBinding" })` | | 改动前评估调用方与风险 | `impact` | `impact({ target: "authorizeBinding", direction: "upstream" })` | | 提交前确认当前 diff 的影响 | `detect_changes` | `detect_changes({ scope: "compare", base_ref: "main" })` | | 跨文件重命名 | `rename` | 使用工具执行,不要文本替换 | 工作约定是:探索陌生逻辑时先用 `query`,修改函数、类或方法前先执行 `impact`;若结果为 HIGH 或 CRITICAL,先确认影响范围再继续。提交前执行 `detect_changes`,核对变更只覆盖预期的符号和执行流程。 ## 环境变量与授权配置 环境变量文件统一放在仓库根目录,供 server、Chrome 扩展和数据库迁移共享: - `.env.example`:提交到仓库的变量模板,不放真实密钥。 - `.env`、`.env.development`:可选的基础或开发环境配置,不提交。 - `.env.local`:本机通用配置,不提交。 文件名统一使用 `.env.example` 和 `.env.development`;不要使用 `.env.Example` 或 `.env.dev`。 开发环境可以从模板开始: ```bash cp .env.example .env.local ``` server 开发入口和迁移命令按以下顺序读取文件,后面的文件覆盖前面的同名变量: `.env` → `.env.local` → `.env.development` → `.env.development.local` ### 必填基础配置 | 变量 | 说明 | | -------------------------------------------- | -------------------------------------------------------------------- | | `HOST` | server 监听地址,例如 `127.0.0.1`。 | | `PORT` | server 监听端口,必须是 `1-65535` 的整数,例如 `7878`。 | | `DATABASE_URL` | Bright PostgreSQL 连接串;启动 server 和执行 migration 都需要。 | | `OSS_BUCKET`、`OSS_ENDPOINT` | Chrome 扩展发布包及本地联调上传所用的 OSS Bucket 与 HTTPS Endpoint。 | | `OSS_ACCESS_KEY_ID`、`OSS_ACCESS_KEY_SECRET` | 仅服务端使用的 OSS V1 下载签名与联调页 server-proxy PUT 凭据。 | 本地默认配置如下: ```dotenv HOST=127.0.0.1 PORT=7878 DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/trade_message_center ``` ### OneTalk 本地开发 `pnpm dev` 只以 `NODE_ENV=development` 启动 Bright server 与 Chrome 扩展。`mind-test-harness` 仅允许在 `NODE_ENV=development` 下运行,是顺道使用的低优先级手工工具;必须另开终端执行 `NODE_ENV=development pnpm dev:harness`,不得在 test、staging 或 production 环境启动。它的失败只报告给该命令,绝不阻断主开发流程。启动后,其 Mind 授权模拟默认监听 `127.0.0.1:8787`,独立 Bright 联调页默认监听 `127.0.0.1:8788`。Bright 通过 `MIND_AUTH_BASE_URL` 访问模拟的两个授权接口,不再需要 `ONETALK_DEV_*` 身份 fixture;该工具不连接 Mind 数据库,也不属于生产授权实现。 联调页的“上传本地文件到 OSS”把原始文件 POST 给 Bright,再由 server 使用本地环境中的 `OSS_*` 凭据 PUT 到 `harness-uploads//`;浏览器不会接触 AccessKey,也不依赖 OSS 浏览器 CORS。成功后页面展示 30 分钟有效的 HTTPS 地址,并自动填入图片或附件发送字段。运行时必须让 `MIND_PAGE_ORIGIN` 与联调页 Origin 精确一致;若四项 `OSS_*` 都未配置,接口返回 `503 oss_upload_unavailable`,不会伪造地址。 本地 `.env.local` 至少应将 `MIND_AUTH_BASE_URL` 指向 mock,并将 `MIND_PAGE_ORIGIN` 指向 Bright 页面地址: ```dotenv MIND_AUTH_BASE_URL=http://127.0.0.1:8787 MIND_PAGE_ORIGIN=http://127.0.0.1:8788 ONETALK_PLUGIN_ORIGINS=chrome-extension://ogdbffjakeeidblabkeakakdecfbcmlf ``` `apps/chrome-extension/.keys/extension-private-key.pem` 是本地生成的扩展私钥,权限应保持为 `600`,不会提交到 Git;生成的 `dist/manifest.json` 内置从该私钥导出的固定公钥,因此从不同目录加载 `apps/chrome-extension/dist/` 不会再导致扩展 ID 变化。当前固定 ID 是 `ogdbffjakeeidblabkeakakdecfbcmlf`。`ONETALK_PLUGIN_ORIGINS` 仍必须是这个扩展的精确 Origin,不能改成任意 Origin;`MIND_PAGE_ORIGIN` 必须是浏览器打开 `mind-test-harness` 页面时的精确 Origin,而不是 Mind 授权模拟的监听地址。 开发 server 会输出 `[mind-auth][diagnostic]`,只包含 `binding/session` endpoint、授权 operation、`request_started/allowed/rejected/request_failed`、HTTP status 和稳定 code,可据此判断请求是否发出、Mind 是否返回以及最终拒绝原因;不包含 Cookie、binding、请求体或原始异常。扩展握手的 Origin 拒绝会出现在 `[onetalk][diagnostic]` 中。 ### Mind/Bright 生产认证边界 生产部署使用同一二级域名下的不同三级域名,例如 `mind.example.com` 与 `bright.example.com`。Mind 登录 Cookie 由 Mind 配置为覆盖两个三级域名的 `Domain=.example.com`、`Path=/`、`Secure`、`HttpOnly` Cookie;Mind 页面调用 Bright HTTP 时使用 `credentials: "include"`,Bright 只允许精确 Mind Origin,并对页面 WS 和扩展 WS 分别执行 Origin allowlist。 Bright 不连接 Mind 数据库、不读取认证视图、不共享 Mind 登录密钥,也不签发 `mind_page` credential。Bright 只把页面请求中的 Cookie 转发给 Mind 的 Session 授权接口,并把插件的 `channelAccountId + binding` 转发给 Mind 的 binding 授权接口;Cookie 不进入 URL、WS payload、日志或持久化数据。 ### Mind 本地测试支架 `mind-test-harness` 是仅供手工测试的独立开发工具:它模拟两个 Mind 授权接口,并提供 Bright HTTP/WS 联调页。Mind 模拟默认监听 `127.0.0.1:8787`,默认 fixture 是账号 `243340382`、`286995452`、binding `binding-123` 和 Cookie `mind_session=mock-valid`;`MIND_MOCK_CHANNEL_ACCOUNT_ID` 支持逗号分隔的账号列表,设置 `MIND_MOCK_*` 环境变量后重启即可模拟 binding/version/cookie 切换。两个 endpoint 还可独立设置结果:`MIND_MOCK_BINDING_RESULT=allow|scope_mismatch|binding_revoked|authorization_unavailable`,`MIND_MOCK_SESSION_RESULT=allow|auth_required|scope_mismatch|authorization_rejected|authorization_unavailable`。 根目录执行 `pnpm dev` 时不会启动支架;需要手工验证时才另开终端在开发环境运行 `NODE_ENV=development pnpm dev:harness`。支架不被任何 workspace 包依赖,不含测试文件,不声明 build 脚本,也不进入根 `dev`、`typecheck`、`test`、`build` 或生产 Docker 镜像。支架错误只在其独立命令中报告,不能改变主流程结果。 直接验证两个接口: ```bash curl -sS http://127.0.0.1:8787/internal/bright/onetalk/authorize-binding \ -H 'content-type: application/json' \ --data '{"channelAccountId":"286995452","binding":"binding-123"}' curl -sS http://127.0.0.1:8787/internal/bright/onetalk/authorize-session \ -H 'content-type: application/json' \ -H 'cookie: mind_session=mock-valid' \ --data '{"channelAccountId":"286995452"}' ``` 手工验证时只允许在开发环境单独运行: ```bash NODE_ENV=development pnpm dev:harness ``` `pnpm --filter @trade-message-center/server dev:mind-http` 是仅供显式测试配置使用的 server 启动脚本;普通 `pnpm dev` 已直接使用 development-only 的 loopback Mind HTTP 配置。支架不模拟真实 Mind 的 Cookie Domain/SameSite/Secure、三级域 CORS/TLS 或 takeover 事务;它只在独立本地页面中暴露 Bright 的手工操作入口。 `mind-test-harness` 仅允许在开发环境参与显式的 `NODE_ENV=development pnpm dev:harness`;它不进入根 `pnpm dev`、`typecheck`、`test` 或 `build`,也不会被 `Dockerfile.server` 复制到生产镜像。完成支架改动无需新增或执行测试;若出现错误,只报告,不阻断主流程。 `VITE_BRIGHT_WEBSOCKET_URL` 用于 Chrome 扩展构建: ```dotenv VITE_BRIGHT_WEBSOCKET_URL=ws://127.0.0.1:7878/ws/plugin ``` 它会在扩展构建时注入,必须使用 `ws://` 或 `wss://`。开发或生产构建未设置该变量时会直接失败,不再回退到代码内置地址。 该变量是四个扩展入口共用的构建配置,应放在根目录 `.env.local`,或通过命令行/CI 环境变量注入。 只有以 `VITE_` 开头的变量会暴露给扩展代码,数据库连接串和 binding 等服务端敏感配置不得使用该前缀。 ### Chrome 扩展下载链接 `GET /api/downloads/chrome-extension` 返回当前 workspace 完整版本的临时 OSS 下载地址: ```json { "version": "0.6.1", "downloadUrl": "https://sinanpilot-bucket.oss-cn-hangzhou.aliyuncs.com/chrome-extension/0.6.1/trade-message-center-chrome-extension-0.6.1.zip?..." } ``` 版本由根 `package.json` 的 `version` 经 CI 注入为 `TMC_PACKAGE_VERSION`,接口和发布目录都不使用 `v` 前缀。OSS 签名参数由服务端签名器计算;访问密钥只保存在服务端运行时环境中。未配置 OSS 访问密钥时,接口明确返回 `503 extension_download_unavailable`,不影响本地其它 server 功能。 ## Workspace 版本 根目录 `package.json` 的 `version` 是 workspace 发布版本的唯一编辑点。根目录的 `dev`、`dev:extension`、`build`、`typecheck`、`test` 和 `format:check` 会自动先执行版本镜像同步,再检查一致性;修改根版本后可以直接运行这些命令,不会因为子包镜像尚未更新而中断。 如需手动执行或单独审计,可使用: ```bash pnpm version:sync pnpm version:check ``` 也可以用一次命令完成“同步后检查”: ```bash pnpm version:ensure ``` 版本必须是 Chrome 扩展和 package metadata 都支持的三段数字版本,例如 `0.1.0`;不使用 prerelease、build metadata 或额外段。协议、配置、IndexedDB 和授权版本仍是独立概念,不随 workspace 版本修改。 ### 准备与发布 在已基于最新 `origin/main` 的发布分支运行下列命令。它默认递增 patch;也可传入 `minor` 或 `major`: ```bash pnpm release pnpm release -- minor pnpm release -- major ``` 该命令要求工作区干净,依次递增根版本、同步所有 workspace package 镜像、运行格式/迁移/类型/测试/构建门禁,确认没有其它持久化文件被修改后,提交 `chore: release ` 并推送当前发布分支。若 commit 前任一步失败,脚本会恢复它写入的版本文件;若 commit 已完成但 push 失败,则保留本地提交并明确报错。不要手工改子包版本或 `dist/manifest.json`。 发布 PR 合入并让本地 `main` 与 `origin/main` 完全一致后,执行: ```bash pnpm release:publish ``` 该命令只创建并推送 `v` tag;现有 tag CI 负责后续构建、数据库门禁、扩展上传和服务部署。 ## 启动 server 与数据库 首次启动或数据库结构有变化时,先执行迁移: ```bash pnpm --filter @trade-message-center/server db:migrate ``` 如需重置本地开发数据库,执行以下命令: ```bash pnpm --filter @trade-message-center/server db:reset ``` 该命令会删除开发库的 `public` 和 `drizzle` schema,重建 `public` schema,并重新执行全部 migration。它只接受 `NODE_ENV=development` 和 loopback 主机(`localhost`、`127.0.0.1` 或 `::1`)的 `DATABASE_URL`;请确保目标是专用的本地开发库,命令不会保留其中的数据。 再从仓库根目录启动 server 和扩展开发构建: ```bash pnpm dev ``` 根命令会统一生成并注入 `BUILD_HASH`,同时启动 server 和 Popup、MAIN world、ISOLATED world、Service Worker 四个扩展 watch 构建。因此扩展开发不要直接运行底层 Vite 命令。 如果只需要重启 Bright server,使用: ```bash pnpm dev:server ``` 该命令直接启动 server watch,不调用 workspace 级 `with-build-hash`,也跳过 server `predev` 的 contract 构建;因此不会重新构建 Chrome 扩展,也不会改变当前扩展产物中的 `BUILD_HASH`。首次 checkout 或修改 `onetalk-contract` 后,请先执行 `pnpm --filter @trade-message-center/onetalk-contract build`。如果 server 需要本地 Mind 测试支架,请另开终端在开发环境运行 `NODE_ENV=development pnpm dev:harness`。 检查 server 是否启动: ```bash curl http://127.0.0.1:7878/health ``` 预期返回: ```json { "status": "ok" } ``` 本地 Bright 联调页由测试支架独立提供: ```text http://127.0.0.1:8788/ ``` 本地 harness 中的 OneTalk account 应包含在 `MIND_MOCK_CHANNEL_ACCOUNT_ID`(默认 `243340382,286995452`)中;生产 Mind 页面不提交可信的 user/workspace header,而由 Cookie Session 授权结果派生。页面会连接配置的 Bright 地址的 `/ws/mind`,插件使用 Bright 的 `/ws/plugin`;`MIND_PAGE_ORIGIN` 必须设为该页面的 Origin。 ## 加载和配置 Chrome 扩展 1. 执行 `pnpm dev`,确认生成 `apps/chrome-extension/dist/`。 2. 打开 `chrome://extensions`,启用“开发者模式”,选择“加载已解压的扩展程序”,加载 `apps/chrome-extension/dist/`。 3. 打开 OneTalk 页面 `https://onetalk.alibaba.com/`,点击扩展图标打开 Popup;Popup 会从页面运行时读取登录人账号 ID(`currentUserAccountId`),不要填写 URL 的 `activeAccountId`。 4. 在 Binding 输入框填入 `MIND_MOCK_BINDING`(默认 `binding-123`)对应的值后保存配置;Popup 首次运行会自动生成 Device ID。URL 中的 `activeAccountId` 仅是当前对话账号。 5. Bright WebSocket URL 仅展示当前构建使用的地址,不在 Popup 中手工填写。配置保存或清除后,Service Worker 会动态建立、重建或关闭 Bright 连接和同步引擎。 扩展配置保存在 `chrome.storage.local`。Popup 不配置 `mindUserId` 或 `workspaceId`;保存 binding 时留空表示保持已有 binding,输入新值才会替换它。清除配置不会重置自动生成的 Device ID。 生产构建: ```bash pnpm build ``` 构建完成后仍可将 `apps/chrome-extension/dist/` 作为未解压扩展加载。 ## 生成并发布 Chrome 扩展 推送指向 `main` 历史的 Git tag 后,生成流程会在发布前检查中的同一次构建基础上打包扩展,并将版本包上传到 OSS: ```text oss:///chrome-extension//trade-message-center-chrome-extension-.zip ``` OSS Bucket、Endpoint 和 Region 已直接写在 `.github/workflows/release_ci.yml` 中。`production` Environment 只需要配置以下密钥: - Secrets:`OSS_ACCESS_KEY_ID`、`OSS_ACCESS_KEY_SECRET`。 上传账号只需要目标 Bucket 对应版本目录的写权限。Git tag 仍用于触发发布和定位源码,但不会作为扩展发布版本;版本目录和文件名使用根 `package.json` 的 `version`,均不添加 `v` 前缀。现有同版本覆盖语义保持不变。 PR 和 `main` 分支推送本身不触发发布。发布新版本时先更新根版本并同步子包,合入 `main` 后再推送对应 tag;不要只更换 tag 名而重复使用旧的根版本。CI 自动构建并打包,不需要提交本地 `dist/` 或手动上传 ZIP。 发布顺序为质量检查 → 上传扩展 ZIP 到 OSS → 部署 server。上传失败时不会部署新服务,避免下载接口指向尚不存在的新版本包。扩展使用 `production` Environment 的 `VITE_BRIGHT_WEBSOCKET_URL` 构建;本地 `.env` 中的开发地址不能用于线上下载包。上线后应实际访问下载接口并下载 ZIP,核对 `manifest.json` 版本及扩展连接地址,不能只以 `/health` 成功判断下载功能可用。 ## Server 数据库迁移 server 使用 Drizzle 的 TypeScript schema 生成版本化 SQL migration。业务表定义放在 `apps/server/src/database/schema/`,迁移文件和元数据放在 `apps/server/drizzle/`,相关命令从仓库根目录执行: ```bash pnpm --filter @trade-message-center/server db:generate pnpm --filter @trade-message-center/server db:check pnpm --filter @trade-message-center/server db:migrate ``` 生产部署时,先以独立的一次性 job 执行 `db:migrate`,成功后再启动或滚动更新 server;应用启动不会自动修改数据库结构。已执行的 migration 不可修改,破坏性变更使用新的补偿 migration 和 expand-contract 流程。 ## 测试与质量检查 常用检查: ```bash pnpm format:check pnpm typecheck pnpm test pnpm build git diff --check ``` ## VSCode 开发环境 本项目统一使用 Oxfmt 格式化代码,缩进为 4 个空格。请在 VSCode 中安装 Oxc 插件 `oxc.oxc-vscode`,否则保存文件时可能使用其它 formatter,导致代码在提交钩子中再次变化。 安装方式: 1. 打开 VSCode 扩展面板(macOS 快捷键:`⇧⌘X`)。 2. 搜索 `Oxc`,安装扩展 `oxc.oxc-vscode`。 3. 重新打开项目窗口,确认右下角或状态栏使用 Oxc formatter。 仓库已经在 `.vscode/settings.json` 中配置 Oxc 保存格式化,并通过 `.oxfmtrc.json` 固定 4 空格规则。项目本地依赖已经包含 Oxfmt,不需要单独全局安装。 如果暂时不安装插件,请关闭 VSCode 的 `formatOnSave`,需要格式化时在项目根目录执行: ```bash pnpm format pnpm format:check ``` 不要同时启用 Prettier、Biome 或 VSCode 内置 TypeScript formatter。 1213