diff --git a/.env.example b/.env.example index d8793f1..88b0812 100644 --- a/.env.example +++ b/.env.example @@ -12,12 +12,17 @@ OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com OSS_ACCESS_KEY_ID= OSS_ACCESS_KEY_SECRET= -# Mind authorization upstream. For local `pnpm dev`, point this at the Mind HTTP mock. +# Mind authorization upstream. When manually running mind-test-harness, point this at its mock. MIND_AUTH_BASE_URL=https://mind.example.com -# Bright API 的 CORS 白名单 +# Bright API 的 CORS 白名单;本地联调页默认运行在 http://127.0.0.1:8788。 MIND_PAGE_ORIGIN=https://mind.example.com ONETALK_PLUGIN_ORIGINS=chrome-extension://ogdbffjakeeidblabkeakakdecfbcmlf MIND_AUTH_TIMEOUT_MS=3000 +# mind-test-harness 的独立联调页目标。该包只供顺道手工测试,不参与根 dev、质量门禁、build 或生产镜像。 +MIND_TEST_HARNESS_HOST=127.0.0.1 +MIND_TEST_HARNESS_PORT=8788 +MIND_TEST_HARNESS_BRIGHT_BASE_URL=http://127.0.0.1:7878 + # 扩展构建时注入的 Bright WebSocket 地址 VITE_BRIGHT_WEBSOCKET_URL=ws://127.0.0.1:7878/ws/plugin diff --git a/.trellis/config.yaml b/.trellis/config.yaml index da139b3..5b929f5 100644 --- a/.trellis/config.yaml +++ b/.trellis/config.yaml @@ -161,6 +161,10 @@ channel: packages: chrome-extension: path: apps/chrome-extension + mind-test-harness: + path: apps/mind-test-harness + onetalk-contract: + path: apps/onetalk-contract server: path: apps/server default_package: @trade-message-center/chrome-extension diff --git a/.trellis/spec/mind-test-harness/development/boundary.md b/.trellis/spec/mind-test-harness/development/boundary.md new file mode 100644 index 0000000..9c74077 --- /dev/null +++ b/.trellis/spec/mind-test-harness/development/boundary.md @@ -0,0 +1,62 @@ +# Mind 测试支架边界与启动契约 + +## 1. Scope / Trigger + +- Trigger:修改 `apps/mind-test-harness/` 的 Mind 外部依赖模拟、Bright 手工联调页面、启动入口或环境变量。 +- Scope:仅开发者手工测试。它可模拟外部 Mind 授权,也可操作/展示 Bright 业务响应;不属于 server、Chrome extension 或生产部署。 + +## 2. Signatures + +```text +pnpm dev:harness +MIND_MOCK_HOST=127.0.0.1 +MIND_MOCK_PORT=8787 +MIND_TEST_HARNESS_HOST=127.0.0.1 +MIND_TEST_HARNESS_PORT=8788 +MIND_TEST_HARNESS_BRIGHT_BASE_URL=http://127.0.0.1:7878 +``` + +- `src/entry.ts` 必须启动 Mind 授权模拟监听器和 Bright 联调页监听器。 +- `pnpm dev` 不得启动、等待或依赖该包;只有显式 `pnpm dev:harness` 才运行它。 +- 联调页使用 `MIND_TEST_HARNESS_BRIGHT_BASE_URL` 请求 Bright HTTP 和 WS;不能再由 `apps/server` 的 `/harness` 路由提供。 + +## 3. Contracts + +- 包可消费 `@trade-message-center/onetalk-contract`,但不提供给其它 workspace 包消费。任何 `apps/*/package.json` 都不得声明 `@trade-message-center/mind-test-harness` 为任一 dependency 类字段。 +- 包内不得有 `test/` 目录、`*.test.*` 文件、`test` 脚本、`build` 脚本、TypeScript `outDir` 或提交的 `dist/`。根 `pnpm build` 和生产 Dockerfile 均不得 filter/copy 该包。 +- 该包是顺道编写的低优先级工具,不得被根 `dev`、`typecheck`、`test` 或 `build` 调度;实现完成不要求测试。其启动或运行失败只在独立命令中报告,不能停止、等待或改变 server/extension 主流程。 +- 浏览器的 Origin 必须与 server 的 `MIND_PAGE_ORIGIN` 精确相等;默认页面地址为 `http://127.0.0.1:8788`。Cookie 快捷入口只写页面所在 loopback host 的开发 Cookie,生产 Cookie 仍由 Mind 登录设置。 +- 该包的启动失败、上游 Bright 拒绝、CORS 拒绝和 WS Origin 拒绝必须原样可见,不得加跨域代理、默认成功或静默 fallback。 + +## 4. Validation & Error Matrix + +| 条件 | 结果 | +| --- | --- | +| `MIND_TEST_HARNESS_PORT` 或 `MIND_MOCK_PORT` 非 0–65535 整数 | 启动前抛出对应环境变量错误 | +| Bright base URL 不是精确 HTTP/HTTPS origin | 启动前抛出 `Invalid MIND_TEST_HARNESS_BRIGHT_BASE_URL` | +| 监听端口已被占用 | 启动失败并关闭已打开的另一监听器 | +| 非 `GET /` 请求联调页监听器 | 返回 404 | +| `MIND_PAGE_ORIGIN` 与页面 Origin 不相等 | Bright 的 CORS/WS 显式拒绝;不得绕过 | +| 支架启动或运行失败 | 仅向 `pnpm dev:harness` 报告;根主流程继续运行 | + +## 5. Good / Base / Bad Cases + +- Good:向支架加入可重复的外部依赖 fixture 或 Bright 页面操作,同时保持它只从共享 contract 读取协议版本和 HTTP route 模板。 +- Base:需要时运行 `pnpm dev:harness` 进行手工 HTTP/页面 smoke;这不是完成条件,也不是生产发布验证。 +- Bad:server import 支架以重新开放 `/harness`;其它包声明它为依赖;把支架加入根 dev/质量门禁;为“覆盖率”在包内加入测试文件;或增加 build/dist 来发布它。 + +## 6. Tests Required + +- `mind-test-harness` 本身不创建或保留测试文件,也不声明 `test` 命令。 +- 完成支架改动无需新增或执行测试,也不得把它接入根质量门禁。若做了手工启动,结果仅作为诊断报告,不构成主流程阻塞条件。 +- 错误必须保留可见的日志/退出信息;不要为了保持支架可用而制造成功 fallback。 + +## 7. Wrong vs Correct + +```ts +// Wrong: 将手工页面重新挂回 server,使 server 依赖测试支架。 +installOneTalkHarnessRoute(app); + +// Correct: 支架自己监听页面端口,server 只保留业务 HTTP/WS 路由。 +const harnessServer = createOneTalkHarnessServer(config); +``` diff --git a/.trellis/spec/mind-test-harness/development/index.md b/.trellis/spec/mind-test-harness/development/index.md new file mode 100644 index 0000000..3797894 --- /dev/null +++ b/.trellis/spec/mind-test-harness/development/index.md @@ -0,0 +1,23 @@ +# Mind 测试支架开发规范 + +> 本规范定义 `@trade-message-center/mind-test-harness` 的唯一用途:人工联调时模拟外部 Mind 依赖,并提供针对 Bright 业务的本地操作页面。它不是产品运行时模块,也不是可复用的 workspace 库。 + +## 当前基线 + +- 包目录为 `apps/mind-test-harness/`;`src/entry.ts` 在显式执行 `pnpm dev:harness` 时启动 Mind 授权模拟和独立 Bright 联调页。 +- 包可以依赖共享协议 `@trade-message-center/onetalk-contract`,但仓库内任何其它包不得依赖它;server 也不得导入它来注册路由或提供页面。 +- 包是顺道编写的低优先级手工工具:不声明 `build` 或 `test` 脚本,不含测试文件,不产生 `dist/`;根 `pnpm dev`、`typecheck`、`test`、`build`、生产 Docker 镜像和发布流程都不包含它。它的失败只报告,绝不阻断主流程。 + +## 规范目录 + +| 文档 | 内容 | 状态 | +| --- | --- | --- | +| [项目级代码架构](../../project/architecture.md) | 所有包共享的职责、依赖方向和源码约定 | 项目级必读 | +| [边界与启动契约](./boundary.md) | 独立性、监听地址、环境变量和验证约束 | 已建立 | + +## 开始前检查 + +1. 先阅读项目级代码架构和 [边界与启动契约](./boundary.md),确认改动只服务人工测试。 +2. 搜索 workspace manifests,确认没有包把 `@trade-message-center/mind-test-harness` 列为 dependency、devDependency、peerDependency 或 optionalDependency。 +3. 不新增 `test/`、`*.test.*`、`build` 脚本、`outDir`、`dist` 或 package export;完成支架改动不要求测试或将它接入根质量门禁,错误只需如实报告。 +4. 修改联调页 Origin 或 Bright 目标地址时,同时核对 server 的 `MIND_PAGE_ORIGIN`,避免以 CORS/WS 绕过掩盖配置错误。 diff --git a/.trellis/spec/server/backend/index.md b/.trellis/spec/server/backend/index.md index 12b5833..33b0a4f 100644 --- a/.trellis/spec/server/backend/index.md +++ b/.trellis/spec/server/backend/index.md @@ -7,7 +7,7 @@ - 包目录是 `apps/server/`,已有 Fastify 应用工厂、健康路由、Bright 读取/联调路由、WebSocket 注册和 PostgreSQL ORM 连接模块。 - 运行时依赖为 `fastify@^5.12.1`、`@fastify/websocket@^11.3.0`、`drizzle-orm@^0.45.2` 和 `postgres@^3.4.9`。 - 根 TypeScript 配置使用严格模式、`ES2022` 和 `NodeNext`,服务端代码应复用该配置。 -- 当前已建立 Drizzle 迁移配置、OneTalk 事实 schema、一次性执行入口和本地 Mind HTTP mock;生产日志库仍未引入。OneTalk 协议使用共享 contract 包,测试使用 Node.js 内置 `node:test`。 +- 当前已建立 Drizzle 迁移配置、OneTalk 事实 schema 和一次性执行入口;本地 Mind 授权模拟与 Bright 联调页归独立的 `mind-test-harness`,不属于 server。生产日志库仍未引入。OneTalk 协议使用共享 contract 包,测试使用 Node.js 内置 `node:test`。 ## 规范目录 diff --git a/.trellis/spec/server/backend/service-foundation.md b/.trellis/spec/server/backend/service-foundation.md index d84191a..fb4a609 100644 --- a/.trellis/spec/server/backend/service-foundation.md +++ b/.trellis/spec/server/backend/service-foundation.md @@ -173,7 +173,7 @@ createApp(config: ServerConfig, dependencies?: AppDependencies): FastifyInstance ### 5. Good / Base / Bad Cases -- Good:`pnpm dev` 设置开发环境并启动本地 Mind HTTP mock,`createApp` 通过固定 HTTP adapter 授权;测试/生产仍按各自 URL/HTTPS 规则运行。 +- Good:需要时显式运行 `pnpm dev:harness` 启动本地 Mind 授权模拟,`createApp` 通过固定 HTTP adapter 授权;`pnpm dev` 与 server/extension 主流程不依赖该低优先级工具,测试/生产仍按各自 URL/HTTPS 规则运行。 - Base:真实 Mind HTTP auth 通过同一个 `createMindAuthorizationReader` 或 `AppDependencies.authorization` 接入,环境选择不改变业务授权调用。 - Bad:在 `createApp` 中无条件 allow、把 `NODE_ENV` 缺失当作 development,或在 HTTP header/host 上推断开发模式。 diff --git a/.trellis/spec/server/frontend/directory-structure.md b/.trellis/spec/server/frontend/directory-structure.md index 02dc26e..d37d44b 100644 --- a/.trellis/spec/server/frontend/directory-structure.md +++ b/.trellis/spec/server/frontend/directory-structure.md @@ -2,17 +2,17 @@ ## 当前状态 -服务端包包含一个只用于本地联调的原生 HTML 页面;它不是独立前端应用,也没有前端构建产物: +服务端包不包含原生 HTML 联调页,也没有前端构建产物: ```text apps/ -└── server/ - └── src/ - └── http/ - └── harness.ts # Fastify /harness 路由、HTML、CSS 和浏览器脚本 +├── server/ +│ └── src/ # Fastify HTTP/WS 和业务实现 +└── mind-test-harness/ + └── src/ # 独立联调页与 Mind 外部依赖模拟 ``` -`harness.ts` 将页面作为静态响应返回。页面通过同源 Bright HTTP/WS 读取会话、历史、插件状态和同步事件;不连接数据库、不读取 Mind legacy 消息、不引入 UI 框架或前端依赖。 +`mind-test-harness` 自己监听页面端口,并通过显式 Bright base URL 读取会话、历史、插件状态和同步事件;不连接数据库、不读取 Mind legacy 消息、不引入 UI 框架或前端依赖。server 不注册 `/harness` 路由,也不导入该包。 ## 边界规则 @@ -22,16 +22,17 @@ apps/ ## 联调页边界 -- 页面脚本只负责输入、请求编排、运行时响应校验、状态展示、历史分页和事实去重;HTTP/WS route、service、repository 保持在 backend 层。 +- 页面脚本只负责输入、请求编排、运行时响应校验、状态展示、历史分页和事实去重;HTTP/WS route、service、repository 保持在 server backend 层。 - 页面使用独立 history cursor 恢复;重连时先重新读取历史,再建立 WS,避免把内存中的事件状态当作可靠存储。 - 页面展示 raw `conversationId`/`messageId`/sender/login/direction/sentAtMs,文本进入 `innerHTML` 前必须转义。 - 发送按钮在当前未实现发送链路时保持禁用,不得伪造成功或写入客户端 outbox。 ## 命名 -包目录为 `server`、包名为 `@trade-message-center/server`;联调页文件命名使用功能名 `harness.ts`。若加入正式 UI,再按其真实入口和功能边界拆分文件。 +包目录为 `server`、包名为 `@trade-message-center/server`。`mind-test-harness` 仅用于开发手工测试,不能被 server 或其它包依赖。若加入正式 UI,再按其真实入口和功能边界拆分文件。 ## 参考文件 - [`apps/server/package.json`](../../../../apps/server/package.json):当前服务端包的真实内容。 +- [`apps/mind-test-harness/package.json`](../../../../apps/mind-test-harness/package.json):独立开发测试工具,不参与构建。 - [`tsconfig.base.json`](../../../../tsconfig.base.json):共享编译配置。 diff --git a/.trellis/spec/server/frontend/index.md b/.trellis/spec/server/frontend/index.md index e2ff3cc..627c653 100644 --- a/.trellis/spec/server/frontend/index.md +++ b/.trellis/spec/server/frontend/index.md @@ -1,11 +1,11 @@ # 服务端前端开发规范 -> `@trade-message-center/server` 仍是纯服务端包,但已包含一个只用于本地验证的原生 HTML harness。这里不定义正式前端框架或生产 UI。 +> `@trade-message-center/server` 是纯服务端包,不包含本地 HTML harness、正式前端框架或生产 UI。 ## 当前基线 -- `apps/server/package.json` 只有 Fastify 依赖,没有 React/Vue/Svelte、样式库或独立前端构建工具;`src/http/harness.ts` 内联提供本地页面。 -- 页面只调用 Bright HTTP/WS,组件、Hook、状态管理和正式 UI 测试约定仍未形成。 +- `apps/server/package.json` 只有 Fastify 依赖,没有 React/Vue/Svelte、样式库或独立前端构建工具。 +- 手工 Bright 联调页面归 `apps/mind-test-harness/` 所有;server 只提供其 HTTP/WS 业务边界。 - TypeScript 仍必须遵循根 `tsconfig.base.json` 的严格模式和 `NodeNext` 模块解析。 ## 规范目录 @@ -29,4 +29,4 @@ ## 文档维护 -当服务端出现正式前端代码后,用真实组件、入口和测试补充本文件;不要把本地 harness 约定扩展成未经任务确认的 UI 框架细节。 +当服务端出现正式前端代码后,用真实组件、入口和测试补充本文件;不要把 `mind-test-harness` 的手工测试能力重新放回 server。 diff --git a/.trellis/spec/server/frontend/quality-guidelines.md b/.trellis/spec/server/frontend/quality-guidelines.md index 5fca044..453e1c1 100644 --- a/.trellis/spec/server/frontend/quality-guidelines.md +++ b/.trellis/spec/server/frontend/quality-guidelines.md @@ -3,7 +3,7 @@ ## 已确认的工具链 - 使用 pnpm `11.7.0`、Node.js `>=22.22.2 <23` 和根级严格 TypeScript 配置。 -- 根级 `dev`、`build`、`typecheck`、`test` 命令只调用实际存在的子包脚本;当前联调页由服务端直接返回,不新增前端构建脚本。 +- 根级 `dev`、`build`、`typecheck`、`test` 命令只调用实际存在的子包脚本;手工联调页由 `mind-test-harness` 独立提供,server 不新增前端构建脚本。 - 根级 `format` 和 `format:check` 使用 Oxfmt;lint-staged 只格式化暂存文件,Husky 在 `pre-commit` 阶段触发该检查。 - 当前没有 lint、组件测试、端到端测试或浏览器构建工具,不能把这些能力写成已经启用的检查项。 @@ -11,10 +11,10 @@ - UI 代码必须通过根 `tsconfig.base.json` 的严格检查,并保持与服务端 API/消息契约的类型边界。 - 用户可见状态至少要考虑加载、空数据、错误和权限失败;实际测试方案确定后补上自动化验证。 -- 原生联调页必须显式展示加载、空数据、插件 online/offline、授权失败、历史读取失败、WS 断线和同步状态;发送能力未接入时保持禁用。 -- 联调页只能调用 Bright HTTP/WS;不得在浏览器脚本中访问数据库、服务端模块、Mind legacy 接口或保存 credential。 +- 联调页必须显式展示加载、空数据、插件 online/offline、授权失败、历史读取失败、WS 断线和同步状态;发送能力未接入时保持禁用。 +- 联调页只能调用 Bright HTTP/WS;不得在浏览器脚本中访问数据库、server 内部模块、Mind legacy 接口或保存 credential。 - 来自 HTTP/WS 的未知数据必须先运行时校验;消息正文进入 DOM 前必须转义,事实去重键必须包含 scope、会话和原始 messageId。 -- 前端专属依赖和脚本写入 `apps/server/package.json`,不要无理由污染根依赖。 +- 联调页专属依赖和脚本写入 `apps/mind-test-harness/package.json`;该包不应声明 build/test 脚本,也不得成为其它包依赖。 - 引入浏览器端能力时,明确哪些代码只能在客户端执行,避免在服务端环境访问浏览器全局对象。 ## 禁止做法 @@ -35,7 +35,7 @@ pnpm test 首次出现服务端前端后,应把实际入口和测试脚本接入 workspace,并记录浏览器/SSR 场景的额外验证方法。 -当前 `/harness` 没有独立浏览器构建或 E2E runner;最低验证包括服务端 typecheck/build/test、`GET /harness` 内容 smoke,以及注入 Mock auth/service 的真实本地 HTTP/WS 网络 smoke。 +当前联调页没有独立浏览器构建或 E2E runner;最低验证包括服务端 typecheck/build/test、`GET http://127.0.0.1:8788/` 内容 smoke,以及真实本地 HTTP/WS 网络 smoke。 ## 评审清单 diff --git a/README.md b/README.md index 1329ad2..dcf6009 100644 --- a/README.md +++ b/README.md @@ -110,17 +110,17 @@ DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/trade_message_center ### OneTalk 本地开发 -`pnpm dev` 会以 `NODE_ENV=development` 启动 Bright server,并同时启动本地 Mind HTTP mock(默认 `127.0.0.1:8787`)。Bright 通过 `MIND_AUTH_BASE_URL` 访问 mock 的两个授权接口,不再需要 `ONETALK_DEV_*` 身份 fixture;mock 只模拟 Mind HTTP 授权结果,不是生产授权实现,也不会连接 Mind 数据库。 +`pnpm dev` 只以 `NODE_ENV=development` 启动 Bright server 与 Chrome 扩展。`mind-test-harness` 是顺道使用的低优先级手工工具,必须另开终端运行 `pnpm dev:harness`;它的失败只报告给该命令,绝不阻断主开发流程。启动后,其 Mind 授权模拟默认监听 `127.0.0.1:8787`,独立 Bright 联调页默认监听 `127.0.0.1:8788`。Bright 通过 `MIND_AUTH_BASE_URL` 访问模拟的两个授权接口,不再需要 `ONETALK_DEV_*` 身份 fixture;该工具不连接 Mind 数据库,也不属于生产授权实现。 本地 `.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:7878 +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` 是浏览器访问 Bright 的页面 Origin,不是 mock 的监听地址。 +`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]` 中。 @@ -130,33 +130,33 @@ ONETALK_PLUGIN_ORIGINS=chrome-extension://ogdbffjakeeidblabkeakakdecfbcmlf Bright 不连接 Mind 数据库、不读取认证视图、不共享 Mind 登录密钥,也不签发 `mind_page` credential。Bright 只把页面请求中的 Cookie 转发给 Mind 的 Session 授权接口,并把插件的 `channelAccountId + binding` 转发给 Mind 的 binding 授权接口;Cookie 不进入 URL、WS payload、日志或持久化数据。 -### Mind HTTP 本地模拟 +### Mind 本地测试支架 -仓库提供一个无状态的本地 Mind HTTP mock,只实现上述两个授权接口。它默认监听 `127.0.0.1:8787`,默认 fixture 是账号 `286995452`、binding `binding-1` 和 Cookie `mind_session=mock-valid`;通过 `MIND_MOCK_*` 环境变量覆盖 fixture 后重启即可模拟 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`。 +`mind-test-harness` 是仅供手工测试的独立开发工具:它模拟两个 Mind 授权接口,并提供 Bright HTTP/WS 联调页。Mind 模拟默认监听 `127.0.0.1:8787`,默认 fixture 是账号 `286995452`、binding `binding-123` 和 Cookie `mind_session=mock-valid`;通过 `MIND_MOCK_*` 环境变量覆盖 fixture 后重启即可模拟 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` 时会先清理各 workspace 包的开发 `dist`,重新构建共享 contract,再自动启动 mock;也可以单独运行 `pnpm dev:mock`。 +根目录执行 `pnpm dev` 时不会启动支架;需要手工验证时才另开终端运行 `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-1"}' + --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"}' ``` -根目录执行 `pnpm dev` 会自动启动 mock 和 Bright;也可以单独运行: +手工验证时单独运行: ```bash -pnpm dev:mock +pnpm dev:harness ``` -`pnpm --filter @trade-message-center/server dev:mind-http` 是仅供显式测试配置使用的 server 启动脚本;普通 `pnpm dev` 已直接使用 development-only 的 loopback Mind HTTP 配置。这个 mock 不模拟真实 Mind 的 Cookie Domain/SameSite/Secure、三级域 CORS/TLS、浏览器页面或 takeover 事务。 +`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-http-mock` 只参与本地 `dev`、类型检查和测试,不参与根 `pnpm build`,也不会被 `Dockerfile.server` 复制到生产镜像。 +`mind-test-harness` 仅参与显式的 `pnpm dev:harness`;它不进入根 `pnpm dev`、`typecheck`、`test` 或 `build`,也不会被 `Dockerfile.server` 复制到生产镜像。完成支架改动无需新增或执行测试;若出现错误,只报告,不阻断主流程。 `VITE_BRIGHT_WEBSOCKET_URL` 用于 Chrome 扩展构建: @@ -232,7 +232,7 @@ pnpm dev 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 HTTP mock,请另开终端运行 `pnpm dev:mock`。 +该命令直接启动 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 测试支架,请另开终端运行 `pnpm dev:harness`。 检查 server 是否启动: @@ -246,20 +246,20 @@ curl http://127.0.0.1:7878/health { "status": "ok" } ``` -本地 Bright 联调页位于: +本地 Bright 联调页由测试支架独立提供: ```text -http://127.0.0.1:7878/harness +http://127.0.0.1:8788/ ``` -本地 harness 中的 OneTalk account 应与 `MIND_MOCK_CHANNEL_ACCOUNT_ID`(默认 `286995452`)一致;生产 Mind 页面不提交可信的 user/workspace header,而由 Cookie Session 授权结果派生。页面 WebSocket 使用 `/ws/mind`,插件使用 `/ws/plugin`。 +本地 harness 中的 OneTalk account 应与 `MIND_MOCK_CHANNEL_ACCOUNT_ID`(默认 `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-1`)对应的值后保存配置;Popup 首次运行会自动生成 Device ID。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。 diff --git a/apps/mind-http-mock/src/entry.ts b/apps/mind-http-mock/src/entry.ts deleted file mode 100644 index f9c6d1a..0000000 --- a/apps/mind-http-mock/src/entry.ts +++ /dev/null @@ -1,65 +0,0 @@ -// 启动 Mind HTTP mock 的本地监听入口 - -import { pathToFileURL } from "node:url"; - -import { createMindAuthorizationServer, parseMindMockConfig } from "./server.ts"; - -export { - createMindAuthorizationServer, - parseMindMockConfig, - MIND_BINDING_AUTHORIZATION_PATH, - MIND_SESSION_AUTHORIZATION_PATH, -} from "./server.ts"; -export type { MindMockAuthorization, MindMockConfig, MindMockPermission } from "./server.ts"; - -const entryPath = process.argv[1]; -const isMainModule = entryPath !== undefined && import.meta.url === pathToFileURL(entryPath).href; - -const listen = async ( - server: ReturnType, - config: ReturnType, -): Promise => { - await new Promise((resolve, reject) => { - const onError = (error: Error): void => { - server.off("listening", onListening); - reject(error); - }; - const onListening = (): void => { - server.off("error", onError); - resolve(); - }; - server.once("error", onError); - server.once("listening", onListening); - server.listen(config.port, config.host); - }); - const address = server.address(); - if (address === null || typeof address === "string") { - throw new Error("Mind HTTP mock did not expose a TCP address"); - } - console.info(`[mind-http-mock] listening on http://${address.address}:${address.port}`); -}; - -const close = async (server: ReturnType): Promise => { - await new Promise((resolve, reject) => { - server.close((error) => (error ? reject(error) : resolve())); - }); -}; - -/** 启动本地 Mind mock,并在进程信号到达时关闭监听器。 */ -export const startMindMock = async (): Promise => { - const config = parseMindMockConfig(); - const server = createMindAuthorizationServer(config); - await listen(server, config); - const shutdown = (): void => { - void close(server).finally(() => process.exit(0)); - }; - process.once("SIGINT", shutdown); - process.once("SIGTERM", shutdown); -}; - -if (isMainModule) { - void startMindMock().catch((error: unknown) => { - console.error(error instanceof Error ? error.message : "Mind HTTP mock failed"); - process.exitCode = 1; - }); -} diff --git a/apps/mind-http-mock/tsconfig.json b/apps/mind-http-mock/tsconfig.json deleted file mode 100644 index 6e30a76..0000000 --- a/apps/mind-http-mock/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": ".", - "types": ["node"], - "rewriteRelativeImportExtensions": true - }, - "include": ["src/**/*.ts"] -} diff --git a/apps/mind-http-mock/package.json b/apps/mind-test-harness/package.json similarity index 78% rename from apps/mind-http-mock/package.json rename to apps/mind-test-harness/package.json index 80a42ec..ec4cd45 100644 --- a/apps/mind-http-mock/package.json +++ b/apps/mind-test-harness/package.json @@ -1,5 +1,5 @@ { - "name": "@trade-message-center/mind-http-mock", + "name": "@trade-message-center/mind-test-harness", "version": "0.8.13", "private": true, "type": "module", @@ -7,8 +7,6 @@ "dev": "node --watch --experimental-strip-types src/entry.ts", "predev": "pnpm --filter @trade-message-center/onetalk-contract build", "start": "node --experimental-strip-types src/entry.ts", - "prebuild": "pnpm --filter @trade-message-center/onetalk-contract build", - "build": "tsc -p tsconfig.json", "pretypecheck": "pnpm --filter @trade-message-center/onetalk-contract build", "typecheck": "tsc --noEmit -p tsconfig.json" }, diff --git a/apps/mind-http-mock/src/authorization/binding.ts b/apps/mind-test-harness/src/authorization/binding.ts similarity index 100% rename from apps/mind-http-mock/src/authorization/binding.ts rename to apps/mind-test-harness/src/authorization/binding.ts diff --git a/apps/mind-http-mock/src/authorization/session.ts b/apps/mind-test-harness/src/authorization/session.ts similarity index 100% rename from apps/mind-http-mock/src/authorization/session.ts rename to apps/mind-test-harness/src/authorization/session.ts diff --git a/apps/mind-http-mock/src/config.ts b/apps/mind-test-harness/src/config.ts similarity index 70% rename from apps/mind-http-mock/src/config.ts rename to apps/mind-test-harness/src/config.ts index bae8f68..50d48e7 100644 --- a/apps/mind-http-mock/src/config.ts +++ b/apps/mind-test-harness/src/config.ts @@ -18,6 +18,9 @@ const DEFAULT_MIND_USER_ID = "mind-user-1"; const DEFAULT_WORKSPACE_ID = "workspace-1"; const DEFAULT_PERMISSIONS = "read,send"; const DEFAULT_COOKIE = "mind_session=mock-valid"; +const DEFAULT_HARNESS_HOST = "127.0.0.1"; +const DEFAULT_HARNESS_PORT = 8788; +const DEFAULT_BRIGHT_BASE_URL = "http://127.0.0.1:7878"; export type MindMockConfig = { host: string; @@ -28,6 +31,12 @@ export type MindMockConfig = { sessionResult?: MindMockSessionResult; }; +export type OneTalkHarnessConfig = { + host: string; + port: number; + brightBaseUrl: string; +}; + const readEnvironmentValue = ( environment: Record, key: string, @@ -37,14 +46,26 @@ const readEnvironmentValue = ( return value === undefined || value === "" ? defaultValue : value; }; -const parsePort = (value: string): number => { +const parsePort = (value: string, key: string): number => { const port = Number(value); if (!Number.isInteger(port) || port < 0 || port > 65535) { - throw new Error("Invalid MIND_MOCK_PORT: expected integer 0-65535"); + throw new Error(`Invalid ${key}: expected integer 0-65535`); } return port; }; +const parseHttpOrigin = (value: string, key: string): string => { + const url = new URL(value); + if ( + (url.protocol !== "http:" && url.protocol !== "https:") || + url.origin !== value || + url.pathname !== "/" + ) { + throw new Error(`Invalid ${key}: expected exact HTTP or HTTPS origin`); + } + return url.origin; +}; + const parsePermissions = (value: string): MindMockPermission[] => { const permissions = value.split(",").map((permission) => permission.trim()); if ( @@ -115,10 +136,38 @@ export const parseMindMockConfig = ( }; return { host: readEnvironmentValue(environment, "MIND_MOCK_HOST", DEFAULT_HOST), - port: parsePort(readEnvironmentValue(environment, "MIND_MOCK_PORT", String(DEFAULT_PORT))), + port: parsePort( + readEnvironmentValue(environment, "MIND_MOCK_PORT", String(DEFAULT_PORT)), + "MIND_MOCK_PORT", + ), cookie: parseFixtureString(environment, "MIND_MOCK_COOKIE", DEFAULT_COOKIE), authorization, bindingResult: parseResult(environment, "MIND_MOCK_BINDING_RESULT", BINDING_MOCK_RESULTS), sessionResult: parseResult(environment, "MIND_MOCK_SESSION_RESULT", SESSION_MOCK_RESULTS), }; }; + +/** 解析独立 Bright 联调页的监听与目标地址。 */ +export const parseOneTalkHarnessConfig = ( + environment: Record = process.env, +): OneTalkHarnessConfig => { + return { + host: readEnvironmentValue(environment, "MIND_TEST_HARNESS_HOST", DEFAULT_HARNESS_HOST), + port: parsePort( + readEnvironmentValue( + environment, + "MIND_TEST_HARNESS_PORT", + String(DEFAULT_HARNESS_PORT), + ), + "MIND_TEST_HARNESS_PORT", + ), + brightBaseUrl: parseHttpOrigin( + readEnvironmentValue( + environment, + "MIND_TEST_HARNESS_BRIGHT_BASE_URL", + DEFAULT_BRIGHT_BASE_URL, + ), + "MIND_TEST_HARNESS_BRIGHT_BASE_URL", + ), + }; +}; diff --git a/apps/mind-test-harness/src/entry.ts b/apps/mind-test-harness/src/entry.ts new file mode 100644 index 0000000..db04625 --- /dev/null +++ b/apps/mind-test-harness/src/entry.ts @@ -0,0 +1,71 @@ +// 启动本地 Mind 授权模拟与 Bright 联调页 + +import type { Server } from "node:http"; +import { pathToFileURL } from "node:url"; + +import { parseOneTalkHarnessConfig } from "./config.ts"; +import { createOneTalkHarnessServer } from "./harness-server.ts"; +import { createMindAuthorizationServer, parseMindMockConfig } from "./server.ts"; + +const entryPath = process.argv[1]; +const isMainModule = entryPath !== undefined && import.meta.url === pathToFileURL(entryPath).href; + +const listen = async (server: Server, config: { host: string; port: number }): Promise => { + await new Promise((resolve, reject) => { + const onError = (error: Error): void => { + server.off("listening", onListening); + reject(error); + }; + const onListening = (): void => { + server.off("error", onError); + resolve(); + }; + server.once("error", onError); + server.once("listening", onListening); + server.listen(config.port, config.host); + }); + const address = server.address(); + if (address === null || typeof address === "string") { + throw new Error("Mind test harness did not expose a TCP address"); + } +}; + +const close = async (server: Server): Promise => { + if (!server.listening) return; + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); +}; + +/** 启动本地 Mind 授权模拟和 Bright 联调页,并在进程信号到达时关闭监听器。 */ +export const startMindTestHarness = async (): Promise => { + const mockConfig = parseMindMockConfig(); + const harnessConfig = parseOneTalkHarnessConfig(); + const mockServer = createMindAuthorizationServer(mockConfig); + const harnessServer = createOneTalkHarnessServer(harnessConfig); + try { + await listen(mockServer, mockConfig); + await listen(harnessServer, harnessConfig); + } catch (error) { + await Promise.all([close(mockServer), close(harnessServer)]); + throw error; + } + console.info( + `[mind-test-harness] Mind authorization mock listening on http://${mockConfig.host}:${mockConfig.port}`, + ); + console.info( + `[mind-test-harness] Bright harness listening on http://${harnessConfig.host}:${harnessConfig.port}`, + ); + const shutdown = (): void => { + void Promise.all([close(mockServer), close(harnessServer)]).finally(() => process.exit(0)); + }; + process.once("SIGINT", shutdown); + process.once("SIGTERM", shutdown); +}; + +if (isMainModule) { + void startMindTestHarness().catch((error: unknown) => { + console.error(error instanceof Error ? error.message : "Mind test harness failed"); + process.exitCode = 1; + }); +} diff --git a/apps/mind-test-harness/src/harness-server.ts b/apps/mind-test-harness/src/harness-server.ts new file mode 100644 index 0000000..09d5e99 --- /dev/null +++ b/apps/mind-test-harness/src/harness-server.ts @@ -0,0 +1,32 @@ +// 提供独立本地 Bright 联调页的 HTTP 监听器 + +import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"; + +import type { OneTalkHarnessConfig } from "./config.ts"; +import { createOneTalkHarnessHtml } from "./harness.ts"; + +const writeNotFound = (response: ServerResponse): void => { + response.writeHead(404, { "content-type": "text/plain; charset=utf-8" }); + response.end("Not Found"); +}; + +const handleRequest = ( + request: IncomingMessage, + response: ServerResponse, + config: OneTalkHarnessConfig, +): void => { + if (request.method !== "GET" || request.url !== "/") { + writeNotFound(response); + return; + } + response.writeHead(200, { + "cache-control": "no-store", + "content-type": "text/html; charset=utf-8", + }); + response.end(createOneTalkHarnessHtml(config.brightBaseUrl)); +}; + +/** 创建独立的 Bright 原生联调页监听器。 */ +export const createOneTalkHarnessServer = (config: OneTalkHarnessConfig): Server => { + return createServer((request, response) => handleRequest(request, response, config)); +}; diff --git a/apps/server/src/http/harness.ts b/apps/mind-test-harness/src/harness.ts similarity index 97% rename from apps/server/src/http/harness.ts rename to apps/mind-test-harness/src/harness.ts index 5e7a115..f10693c 100644 --- a/apps/server/src/http/harness.ts +++ b/apps/mind-test-harness/src/harness.ts @@ -1,12 +1,13 @@ -// 提供本地 Bright 原生联调页面 +// 提供独立本地 Bright 原生联调页面 -import type { FastifyInstance } from "fastify"; +import { + ONETALK_CONVERSATIONS_ROUTE, + ONETALK_PROTOCOL_VERSION, +} from "@trade-message-center/onetalk-contract"; -import { ONETALK_PROTOCOL_VERSION } from "@trade-message-center/onetalk-contract"; - -import { ONETALK_CONVERSATIONS_ROUTE } from "./onetalk.ts"; - -export const ONE_TALK_HARNESS_HTML = String.raw` +export const createOneTalkHarnessHtml = ( + brightBaseUrl: string, +): string => String.raw` @@ -47,7 +48,7 @@ export const ONE_TALK_HARNESS_HTML = String.raw`

Bright OneTalk 联调页

-

页面只请求 Bright HTTP/WS;不连接数据库,也不读取 Mind legacy 消息。

+

页面只请求配置的 Bright HTTP/WS;不连接数据库,也不读取 Mind legacy 消息。

账号与连接

@@ -92,6 +93,7 @@ export const ONE_TALK_HARNESS_HTML = String.raw`
`; - -/** 安装原生 HTML harness 页面路由。 */ -export const installOneTalkHarnessRoute = (app: FastifyInstance): void => { - app.get("/harness", async (_request, reply) => { - return reply - .type("text/html; charset=utf-8") - .header("cache-control", "no-store") - .send(ONE_TALK_HARNESS_HTML); - }); -}; diff --git a/apps/mind-http-mock/src/model.ts b/apps/mind-test-harness/src/model.ts similarity index 100% rename from apps/mind-http-mock/src/model.ts rename to apps/mind-test-harness/src/model.ts diff --git a/apps/mind-http-mock/src/request.ts b/apps/mind-test-harness/src/request.ts similarity index 100% rename from apps/mind-http-mock/src/request.ts rename to apps/mind-test-harness/src/request.ts diff --git a/apps/mind-http-mock/src/response.ts b/apps/mind-test-harness/src/response.ts similarity index 100% rename from apps/mind-http-mock/src/response.ts rename to apps/mind-test-harness/src/response.ts diff --git a/apps/mind-http-mock/src/server.ts b/apps/mind-test-harness/src/server.ts similarity index 100% rename from apps/mind-http-mock/src/server.ts rename to apps/mind-test-harness/src/server.ts diff --git a/apps/mind-test-harness/tsconfig.json b/apps/mind-test-harness/tsconfig.json new file mode 100644 index 0000000..9847948 --- /dev/null +++ b/apps/mind-test-harness/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "allowImportingTsExtensions": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/apps/onetalk-contract/src/model.ts b/apps/onetalk-contract/src/model.ts index b10fcb2..b7a0bca 100644 --- a/apps/onetalk-contract/src/model.ts +++ b/apps/onetalk-contract/src/model.ts @@ -4,6 +4,13 @@ import type { OneTalkCenterMessage, OneTalkMessageContent } from "./content.ts"; export const ONETALK_PROTOCOL_VERSION = 3 as const; +export const ONETALK_CONVERSATIONS_ROUTE = + "/api/bright/onetalk/accounts/:channelAccountId/conversations"; +export const ONETALK_CONVERSATION_ROUTE = + "/api/bright/onetalk/accounts/:channelAccountId/conversations/:conversationId"; +export const ONETALK_HISTORY_ROUTE = + "/api/bright/onetalk/accounts/:channelAccountId/conversations/:conversationId/messages"; + export const ONETALK_CONNECTION_TYPES = ["plugin", "mind_page"] as const; export type OneTalkConnectionType = (typeof ONETALK_CONNECTION_TYPES)[number]; diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index b176011..5024e93 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -14,7 +14,6 @@ import { } from "./mind-authorization.ts"; import { installChromeExtensionDownloadRoute } from "./http/chrome-extension-download.ts"; import { installHealthRoute } from "./http/health.ts"; -import { installOneTalkHarnessRoute } from "./http/harness.ts"; import { installOneTalkReadRoutes } from "./http/onetalk.ts"; import { createOneTalkReadRepository, @@ -88,7 +87,6 @@ export const createApp = ( installHealthRoute(app); installChromeExtensionDownloadRoute(app, config.chromeExtensionDownload); - installOneTalkHarnessRoute(app); const oneTalkRegistry = installWebsocket(app, { authorization, service: oneTalkService, diff --git a/apps/server/src/http/onetalk.ts b/apps/server/src/http/onetalk.ts index 1ec81e8..0c4daa7 100644 --- a/apps/server/src/http/onetalk.ts +++ b/apps/server/src/http/onetalk.ts @@ -1,7 +1,12 @@ // 提供 Bright 受权会话读取 HTTP 边界 import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; -import { ONETALK_PROTOCOL_VERSION } from "@trade-message-center/onetalk-contract"; +import { + ONETALK_CONVERSATIONS_ROUTE, + ONETALK_CONVERSATION_ROUTE, + ONETALK_HISTORY_ROUTE, + ONETALK_PROTOCOL_VERSION, +} from "@trade-message-center/onetalk-contract"; import type { OneTalkAuthorizationDecision, OneTalkAuthorizationReader, @@ -17,13 +22,6 @@ import { import type { OneTalkConnectionRegistry } from "../websocket/registry.ts"; import type { OneTalkCutoverPolicy } from "../cutover-policy.ts"; -export const ONETALK_CONVERSATIONS_ROUTE = - "/api/bright/onetalk/accounts/:channelAccountId/conversations"; -export const ONETALK_CONVERSATION_ROUTE = - "/api/bright/onetalk/accounts/:channelAccountId/conversations/:conversationId"; -export const ONETALK_HISTORY_ROUTE = - "/api/bright/onetalk/accounts/:channelAccountId/conversations/:conversationId/messages"; - const SUMMARY_PURPOSE = "communication_summary_read"; const HISTORY_INCOMPLETE_RETRY_AFTER_SECONDS = 30; const MINIMUM_PAGE_LIMIT = 1; diff --git a/apps/server/test/harness-runtime.test.ts b/apps/server/test/harness-runtime.test.ts deleted file mode 100644 index 77c167e..0000000 --- a/apps/server/test/harness-runtime.test.ts +++ /dev/null @@ -1,448 +0,0 @@ -// 验证原生 harness 的异步状态边界 - -import assert from "node:assert/strict"; -import vm from "node:vm"; -import test from "node:test"; - -import { ONE_TALK_HARNESS_HTML } from "../src/http/harness.ts"; - -const HARNESS_SCRIPT = ONE_TALK_HARNESS_HTML.match(/