@csntgao/uni-base 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +4 -0
- package/.prettierrc.json +6 -0
- package/AGENTS.md +519 -0
- package/CLAUDE.md +512 -0
- package/README.md +734 -0
- package/docs//346/225/260/345/255/227/345/221/230/345/267/245/345/256/214/346/225/264/346/212/275/345/261/211/346/216/245/345/205/245/350/257/264/346/230/216.md +110 -0
- package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/215/225/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +79 -0
- package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/244/232/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +93 -0
- package/docs//350/264/246/346/210/267/344/270/216/347/231/273/345/275/225/347/273/204/344/273/266/345/212/250/346/200/201/346/216/245/345/205/245/350/257/264/346/230/216.md +201 -0
- package/eslint.config.js +30 -0
- package/examples/vanilla/index.html +33 -0
- package/examples/vanilla/main.js +247 -0
- package/examples/vanilla/package.json +15 -0
- package/examples/vanilla/public/runtime-config.js +10 -0
- package/examples/vanilla/style.css +99 -0
- package/package.json +31 -0
- package/packages/core/package.json +30 -0
- package/packages/core/src/client.ts +1132 -0
- package/packages/core/src/customer-support-client.ts +825 -0
- package/packages/core/src/customer-support-types.ts +123 -0
- package/packages/core/src/index.ts +79 -0
- package/packages/core/src/notification-center-client.ts +462 -0
- package/packages/core/src/notification-center-types.ts +154 -0
- package/packages/core/src/protocol.ts +237 -0
- package/packages/core/src/types.ts +170 -0
- package/packages/core/tests/client.test.ts +760 -0
- package/packages/core/tests/customer-support-client.test.ts +576 -0
- package/packages/core/tests/fake-websocket.ts +55 -0
- package/packages/core/tests/notification-center-client.test.ts +467 -0
- package/packages/core/tests/protocol.test.ts +124 -0
- package/packages/core/tsconfig.json +7 -0
- package/packages/core/tsup.config.ts +11 -0
- package/packages/web-components/package.json +35 -0
- package/packages/web-components/scripts/verify-runtime-build.mjs +111 -0
- package/packages/web-components/src/account-context.ts +1540 -0
- package/packages/web-components/src/application-switcher-transport.ts +243 -0
- package/packages/web-components/src/application-switcher.ts +1103 -0
- package/packages/web-components/src/chat.ts +1754 -0
- package/packages/web-components/src/customer-support-chat.ts +862 -0
- package/packages/web-components/src/customer-support-transport.ts +269 -0
- package/packages/web-components/src/index.ts +256 -0
- package/packages/web-components/src/notification-center.ts +1066 -0
- package/packages/web-components/src/notification-popover.ts +615 -0
- package/packages/web-components/src/organization-multi-employee-selector.ts +1564 -0
- package/packages/web-components/src/organization-onboarding-transport.ts +54 -0
- package/packages/web-components/src/organization-onboarding.ts +1216 -0
- package/packages/web-components/src/organization-selector.ts +534 -0
- package/packages/web-components/src/organization-single-employee-selector.ts +114 -0
- package/packages/web-components/src/prototype-icons.ts +242 -0
- package/packages/web-components/src/safe-image-url.ts +29 -0
- package/packages/web-components/src/safe-markdown.ts +192 -0
- package/packages/web-components/src/uniplat-base-account-context-transport.ts +625 -0
- package/packages/web-components/src/uniplat-base-application-switcher-transport.ts +418 -0
- package/packages/web-components/src/uniplat-base-application-ticket.ts +254 -0
- package/packages/web-components/src/uniplat-base-identity-ticket.ts +202 -0
- package/packages/web-components/src/uniplat-base-notification-center-transport.ts +378 -0
- package/packages/web-components/src/uniplat-base-organization-multi-employee-selector-transport.ts +394 -0
- package/packages/web-components/src/uniplat-base-organization-onboarding-transport.ts +183 -0
- package/packages/web-components/src/uniplat-base-organization-single-employee-selector-transport.ts +14 -0
- package/packages/web-components/src/user-login-transport.ts +563 -0
- package/packages/web-components/src/user-login.ts +2027 -0
- package/packages/web-components/src/user-menu.ts +880 -0
- package/packages/web-components/tests/account-context.test.ts +798 -0
- package/packages/web-components/tests/application-switcher.test.ts +562 -0
- package/packages/web-components/tests/chat.test.ts +1130 -0
- package/packages/web-components/tests/customer-support-chat.test.ts +428 -0
- package/packages/web-components/tests/customer-support-transport.test.ts +79 -0
- package/packages/web-components/tests/notification-center-transport.test.ts +271 -0
- package/packages/web-components/tests/notification-center.test.ts +429 -0
- package/packages/web-components/tests/notification-popover.test.ts +283 -0
- package/packages/web-components/tests/organization-multi-employee-selector-transport.test.ts +332 -0
- package/packages/web-components/tests/organization-multi-employee-selector.test.ts +225 -0
- package/packages/web-components/tests/organization-onboarding.test.ts +260 -0
- package/packages/web-components/tests/organization-selector.test.ts +127 -0
- package/packages/web-components/tests/organization-single-employee-selector.test.ts +145 -0
- package/packages/web-components/tests/uniplat-base-account-context-transport.test.ts +496 -0
- package/packages/web-components/tests/uniplat-base-application-switcher-transport.test.ts +598 -0
- package/packages/web-components/tests/uniplat-base-application-ticket.test.ts +233 -0
- package/packages/web-components/tests/uniplat-base-identity-ticket.test.ts +226 -0
- package/packages/web-components/tests/uniplat-base-organization-onboarding-transport.test.ts +227 -0
- package/packages/web-components/tests/user-login-transport.test.ts +443 -0
- package/packages/web-components/tests/user-login.test.ts +626 -0
- package/packages/web-components/tests/user-menu.test.ts +271 -0
- package/packages/web-components/tsconfig.json +8 -0
- package/packages/web-components/tsup.config.ts +12 -0
- package/packages/web-components/tsup.runtime.config.ts +37 -0
- package/pnpm-workspace.yaml +6 -0
- package/tsconfig.base.json +21 -0
- package/vitest.config.ts +20 -0
- package//344/272/244/346/216/245/346/226/207/346/241/243.md +254 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
本文档为 Claude Code 在 `uni-base` 仓库工作时提供指导。除非用户明确要求,所有代码、配置、测试和文档改动都应限制在本仓库内。
|
|
4
|
+
|
|
5
|
+
> 本文件与 `AGENTS.md` 内容一致,二者是同一套仓库规范;修改其一时须同步另一份,避免两份指导漂移。
|
|
6
|
+
|
|
7
|
+
## 项目定位
|
|
8
|
+
|
|
9
|
+
`uni-base` 是一个独立的 TypeScript 浏览器 SDK workspace,用于让原生 HTML、Vue 和 Nuxt 页面嵌入 Uniplat 数字员工能力。
|
|
10
|
+
|
|
11
|
+
当前交付包括:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
packages/core @uniplat/ai-employee-client
|
|
15
|
+
packages/web-components @uniplat/ai-employee-web-components
|
|
16
|
+
examples/vanilla 原生 HTML / JavaScript 接入示例
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
仓库名称已经改为 `uni-base`,但现有 npm 包名、数字员工类型名和 HTML 标签名是公开契约,未经明确的破坏性变更需求不得随仓库名称一起改名。
|
|
20
|
+
|
|
21
|
+
当前注册的 Web Component:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<uniplat-ai-employee-chat></uniplat-ai-employee-chat>
|
|
25
|
+
<uniplat-customer-support-chat></uniplat-customer-support-chat>
|
|
26
|
+
<uniplat-user-login></uniplat-user-login>
|
|
27
|
+
<uniplat-organization-selector></uniplat-organization-selector>
|
|
28
|
+
<uniplat-account-context></uniplat-account-context>
|
|
29
|
+
<uniplat-user-menu></uniplat-user-menu>
|
|
30
|
+
<uniplat-notification-center></uniplat-notification-center>
|
|
31
|
+
<uniplat-notification-popover></uniplat-notification-popover>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
工作台应用切换仅内置于 `<uniplat-account-context>`,不提供或注册独立 `<uniplat-application-switcher>` 标签。内嵌面板通过 `workbench` 的 `getAccessToken()` 和 transport 加载当前 JWT 可访问的应用入口,采用固定高度的 3 列应用网格;不足 9 个入口时必须保留空槽,超过 9 个时在应用区滚动显示全部有效入口,不能截断。内嵌面板不显示组织选择器,因此只请求应用列表;组织上下文由 Account Context 管理。UniplatBase 应用跳转使用短期一次性 ticket,不替换当前 Host JWT,也不应与聊天组件共享状态机。
|
|
35
|
+
|
|
36
|
+
`<uniplat-user-login>` 必须严格复刻亲亲企服 40.1 注册登录原型,提供密码、验证码和扫码三个固定页签、逐字段校验错误态及注册成功过渡页。验证码登录只包含手机号和短信验证码,不得因为接口现状增加图形验证码或其他原型中不存在的字段;旧版 `createCaptcha()` 仅为源码兼容保留,组件不得调用。自定义 Host transport 的密码与扫码方法保持可选,未提供时对应页签保持显示但禁用。UniplatBase 专用 transport 已复用短信验证码及统一 `login-application` 接口提供三种登录能力;组件本身不得保存 JWT,登录结果必须先交给宿主 `setAccessToken()`,再派发不含凭据的成功事件。后端明确返回新用户时显示 2 秒过渡页,并通过 `onboarding-requested` 请求 Host 调用 `<uniplat-account-context>.startOrganizationOnboarding()`。
|
|
37
|
+
|
|
38
|
+
组织入驻不是公开 Web Component。40.1 原型 02、A/B 企业/团队创建分支、校验/成功状态及 4.1 无归属提示均由 `<uniplat-account-context>` 内部实现;Host 在其 `organizationOnboarding.transport` 中注入创建能力,不能使用或注册 `<uniplat-organization-onboarding>`。点击账户菜单或个人态的“创建团队 / 企业”必须直接启动内部流程,不得向 Host 派发 `organization-create-requested` 或 `create-organization-requested`。真实 UniplatBase transport 复用 `createUniplatBaseOrganizationOnboardingTransport()` 与 `system.org/create_organization`。邀请码加入、企业认证、团队升级和邀请成员等未展开页面仍派发业务事件,由 Host 实现。
|
|
39
|
+
|
|
40
|
+
`<uniplat-organization-selector>` 覆盖 40.1 原型 5.1 多归属选择卡片,显示 Host 提供的完整企业/团队列表、类型、认证状态、角色、成员数和最近进入信息。选中与记住偏好只在组件内临时维护,点击“进入”后交由 Host 完成登录态切换。
|
|
41
|
+
|
|
42
|
+
`<uniplat-account-context>` 是 40.1 原型导航右侧的统一账户工作台组件,不渲染品牌和全局菜单。它在一个 Host 元素内包含工作台入口、账户胶囊、应用入口浮层和 6.7 企业/团队切换浮层;两个浮层互斥并绝对定位,不改变导航布局。组件根据 Host 提供的 `applicationName` 和账户 transport 自行加载当前应用对应的组织投影与企业认证状态;用户资料复用 `list_available_organizations` 顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得再请求 `profile_summary_v2`,Host 不再组装这些展示数据。它支持无归属个人态、企业未认证/审核中/未通过/已认证/认证过期和团队态;组织切换由 Host 响应事件,应用加载与 ticket 跳转复用独立的 Application Switcher transport。选中组织时,账户胶囊和菜单资料区必须使用服务端投影中该组织的员工姓名与头像;没有选中组织或该组织没有员工身份时,回退到用户资料。组织列表严格使用原型中的企业建筑与团队成员图标,不得替换成员工头像或组织首字。
|
|
43
|
+
|
|
44
|
+
`<uniplat-notification-center>` 是内嵌通知中心面板,复刻 AE-Enterprise-Admin 通知中心原型的两栏收件箱:面板顶栏、五类页签、列表、详情、关联资源、附件下载与“全部标已读”。**组件和 core 都不建立 WebSocket 连接**:Host 保留自己唯一的 `/api/user-realtime/ws` 连接并订阅 `notification` channel,把事件原始负载通过 `applyRealtimeEvent()` 喂给组件;`authenticate` 首帧、心跳、游标、重连和断连提示全部归 Host。组件只负责收件箱投影、增量更新和未读数,顶栏铃铛仍属 Host chrome。不得为通知新增第二条 socket 或平行实时通道。
|
|
45
|
+
|
|
46
|
+
`<uniplat-notification-popover>` 是 40 号官网原型导航铃铛下的小型消息通知浮层,固定显示最近 4 条未读、未读总数、全部已读、空态和“查看全部消息”。它与完整通知中心复用同一个 `NotificationCenterTransport`、`createUniplatBaseNotificationCenterTransport()` 和 Host 转发的 `notification` 实时事件,不新增接口或 WebSocket。`applicationName` 非空时按应用筛选,显式传空字符串时按当前用户和当前组织加载全应用消息,专用 transport 必须省略 `application_name`;全应用实时事件不得把单应用 `unread_count` 当作聚合数,必须重新请求聚合未读数。组件初始关闭,由 Host 铃铛调用 `open()/close()/toggle()`;打开后必须在下一宏任务由所属 `document` 的点击监听判断 `composedPath()`,不得把调用打开方法的当前铃铛点击误判为外部点击,也不得只依赖可能被 Host `overflow/transform` 限制的固定遮罩;关闭或移除时必须解除监听并取消待执行任务。Host 只负责锚点定位及响应 `notification-center-requested` 打开完整通知页。点击单条只向该事件传稳定 `notificationId`,不得透传标题、正文、JWT、transport 或原始响应。原型图标使用 Tabler Icons 2.47.0 原始 SVG 路径。
|
|
47
|
+
|
|
48
|
+
`<uniplat-user-menu>` 作为独立兼容标签保留,但新导航接入应只配置 `<uniplat-account-context>`。User Menu 与统一组件内部的 40.1 6.7 菜单使用同一实现,显示脱敏账户摘要、个人账号设置、随当前组织变化的员工信息入口、企业/团队混合列表、认证状态、创建和退出登录;它不请求接口、不接收 JWT,所有动作只派发事件。工作台应用面板为 Account Context 内部实现,不暴露独立标签。
|
|
49
|
+
|
|
50
|
+
## 技术栈
|
|
51
|
+
|
|
52
|
+
- pnpm workspace
|
|
53
|
+
- TypeScript 严格模式,目标为 ES2022,模块格式为 ESM
|
|
54
|
+
- Lit 3 Web Components
|
|
55
|
+
- tsup 构建 npm 包,输出 ESM、类型声明和 source map
|
|
56
|
+
- Vite 构建 `examples/vanilla`
|
|
57
|
+
- Vitest;组件测试使用 happy-dom
|
|
58
|
+
- ESLint 9 与 Prettier
|
|
59
|
+
|
|
60
|
+
`packages/core` 不得依赖 Lit、Vue、React、Pinia或其他 UI 框架。`packages/web-components` 可以依赖 Lit 和 core。框架专用封装不属于当前范围。
|
|
61
|
+
|
|
62
|
+
## 开发命令
|
|
63
|
+
|
|
64
|
+
在仓库根目录执行:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pnpm install
|
|
68
|
+
pnpm dev # 先构建两个 SDK 包,再启动 vanilla 示例
|
|
69
|
+
pnpm lint # ESLint 检查,不自动修复
|
|
70
|
+
pnpm format # Prettier 写入格式化
|
|
71
|
+
pnpm format:check # Prettier 只检查
|
|
72
|
+
pnpm typecheck # core 与 web-components 类型检查
|
|
73
|
+
pnpm test # 全量单元测试
|
|
74
|
+
pnpm test:watch # 监听测试
|
|
75
|
+
pnpm build # core → web-components → vanilla 顺序构建
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
提交或交付前至少运行:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pnpm format:check
|
|
82
|
+
pnpm lint
|
|
83
|
+
pnpm typecheck
|
|
84
|
+
pnpm test
|
|
85
|
+
pnpm build
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
如果只改文档,可以按影响缩小验证范围;如果改动公开 API、协议、生命周期、构建配置或组件注册,必须跑完整检查。
|
|
89
|
+
|
|
90
|
+
任何代码修改在验证通过后都必须重新构建并重新打包两个可发布 npm 包;之前生成的 tarball 自代码再次变更起立即视为过期,不能继续交付或同步给调用方:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pnpm build
|
|
94
|
+
pnpm --filter @uniplat/ai-employee-client pack
|
|
95
|
+
pnpm --filter @uniplat/ai-employee-web-components pack
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
该规则适用于 core、Web Components、示例及构建配置等所有代码变更,不因本次修改只涉及其中一个 workspace 而省略另一个发布包。纯文档修改不要求重新打包。向其他项目同步 tarball 时仍须遵守跨项目修改边界,并同步更新调用方依赖声明、锁文件和包校验值。
|
|
99
|
+
|
|
100
|
+
用户已持续授权本仓库在每次代码修改完成后直接发布,无需为同一发布流程逐次重复确认。此授权还包括修复本身:一旦确认了某个问题的修法,直接实现并进入完整交付流水线(验证 → 重建 → 重新打包 → 版本递增 → 提交 → 发布),不得在提出修法后停下等待用户逐次确认。只有在发布目标或凭据缺失(外部阻塞)、涉及破坏性变更、或需要跨项目改动时才停下说明。工作时不得在验证、构建或本地打包完成后停止;必须继续完成版本递增、提交和发布。发布范围至少包括 Web Components 的版本化独立浏览器运行时;修改到 npm 包时还必须发布所有受影响的 npm 包,未受影响的包仍按上述规则重新打包但不强制制造无内容版本。纯文档修改不触发版本递增或发布。
|
|
101
|
+
|
|
102
|
+
直接发布必须遵守以下边界:
|
|
103
|
+
|
|
104
|
+
1. 发布前运行并通过本文件要求的完整检查,重新构建并重新打包;只发布已提交且工作树范围明确的代码。
|
|
105
|
+
2. 发布前查询目标端版本;已存在的版本不得覆盖,必须先按 SemVer 递增版本并重新执行验证、构建和打包。
|
|
106
|
+
3. 运行时发布必须先上传并验证 `dist/runtime/releases/<version>/`,确认可匿名读取且内容正确后,才原子切换 `current.json`;不得先更新清单。
|
|
107
|
+
4. npm 发布只能使用仓库明确配置的 registry 和当前机器已认证的账号;运行时发布只能使用明确配置的静态发布目标。不得猜测 registry、服务器、bucket、路径或凭据,也不得把 token、密码或密钥写入仓库、命令输出或日志。
|
|
108
|
+
5. 如果发布目标、权限或必要配置缺失,发布视为被外部条件阻塞:保留已验证产物,明确列出用户需要完成的一次性配置;配置就绪后继续发布,不能声称已经发布。
|
|
109
|
+
6. 发布完成后必须从远端重新读取 npm 版本、`current.json` 和版本化模块进行验证,并报告实际版本、地址和校验结果。
|
|
110
|
+
|
|
111
|
+
当前已配置的发布目标:
|
|
112
|
+
|
|
113
|
+
- 运行时静态发布目标为 `https://10.10.10.131:8787`。发布后必须校验 `https://10.10.10.131:8787/current.json` 与 `https://10.10.10.131:8787/releases/<version>/index.js` 可匿名读取且内容正确。该服务未常驻,未启动时按外部阻塞处理,不得声称已发布。
|
|
114
|
+
- 启动方式:`node /Users/gaostudio/.uni-base-runtime/serve.mjs`(后台运行)。该脚本用 `node:https` 静态 serve `serve` 目录,`current.json` 返回 `Cache-Control: no-store`、版本目录 `immutable`、对任意 Origin 开放 CORS,并做路径穿越防护。
|
|
115
|
+
- serve 目录:`/Users/gaostudio/uni-base/dist/runtime`(即 Web Components 构建产物)。
|
|
116
|
+
- 证书与私钥:`/Users/gaostudio/.uni-base-runtime/10.10.10.131+2.pem` 与 `10.10.10.131+2-key.pem`,由 mkcert 签发的开发证书,SAN 覆盖 `localhost`、`10.10.10.131`、`127.0.0.1`。脚本与证书都放在仓库外的 `~/.uni-base-runtime/`,私钥属凭据不得写入仓库。
|
|
117
|
+
- 客户端需信任的根证书:`/Users/gaostudio/Library/Application Support/mkcert/rootCA.pem`。该证书只在安装了此 rootCA 的机器上被信任;其它机器的调用方需先安装该 rootCA,或改用正式证书。
|
|
118
|
+
- npm registry 当前为 `https://registry.npmmirror.com` 且本机未登录;该镜像通常只读。npm 发布在用户配置可写 registry 并完成登录前一律视为外部阻塞,不得声称 npm 发布成功。
|
|
119
|
+
|
|
120
|
+
## Workspace 边界
|
|
121
|
+
|
|
122
|
+
### `packages/core`
|
|
123
|
+
|
|
124
|
+
- 负责宿主 transport、会话绑定、历史消息、HTTP 发送、WebSocket 协议、连接状态、心跳、重连、去重和销毁。
|
|
125
|
+
- 保持 UI 无关,不读取或操作聊天 DOM,不引入 Lit。
|
|
126
|
+
- 浏览器能力必须可注入或可测试,例如 `webSocketFactory`。
|
|
127
|
+
- 公开类型从 `src/index.ts` 导出,不要求使用者从内部文件导入。
|
|
128
|
+
- 新增状态或事件时同时更新类型、实现、导出和测试。
|
|
129
|
+
|
|
130
|
+
### `packages/web-components`
|
|
131
|
+
|
|
132
|
+
- 使用标准 Custom Elements 和 Shadow DOM,不能污染宿主页面全局 CSS。
|
|
133
|
+
- 组件只通过公开的 core API 与数字员工能力交互,不复制 WebSocket 状态机。
|
|
134
|
+
- 新组件应放在独立源码文件中,并从 `src/index.ts` 导出和注册。
|
|
135
|
+
- 自定义元素名称必须包含 `uniplat-` 前缀,并在 `HTMLElementTagNameMap` 中声明。
|
|
136
|
+
- 注册前必须用 `customElements.get()` 防止重复定义;访问 `customElements` 前必须判断运行环境,避免模块导入阶段产生 SSR 错误。
|
|
137
|
+
- Lit 响应式字段使用 `declare` 加构造器赋初值,避免原生 class field 遮蔽 Lit accessor。
|
|
138
|
+
- 用户输入和服务端文本使用 Lit 普通文本插值,不能直接注入未净化 HTML。
|
|
139
|
+
- 组件从 DOM 移除时必须关闭客户端、WebSocket 和定时器,并解除监听。
|
|
140
|
+
|
|
141
|
+
### `examples/vanilla`
|
|
142
|
+
|
|
143
|
+
- 示例必须消费 workspace 包的 `exports` / `dist`,禁止直接引用 `packages/*/src`。
|
|
144
|
+
- `pnpm dev` 和根构建脚本负责先构建依赖包。
|
|
145
|
+
- 运行时宿主地址放在 `public/runtime-config.js`;文件中只能放地址、代码和非敏感配置,不能放真实 JWT 或密钥。
|
|
146
|
+
- 示例 transport 只是宿主 API 适配层。宿主接口不同时应改 transport,不应把 HR-SaaS 或 Kanban 内部 HMAC 逻辑搬进 SDK。
|
|
147
|
+
- 生产示例使用 `wss:`;`ws:` 只允许本地开发。
|
|
148
|
+
|
|
149
|
+
## Kanban 实时协议
|
|
150
|
+
|
|
151
|
+
浏览器直接连接:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
/api/user-realtime/ws
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
以下约束属于稳定协议,修改前必须核对 Kanban 当前实现和联合需求:
|
|
158
|
+
|
|
159
|
+
1. WebSocket URL 只允许 `ws:` 或 `wss:`,且不得包含 query、hash、用户名或密码。
|
|
160
|
+
2. 不得申请、拼接或携带 WebSocket ticket。
|
|
161
|
+
3. socket 打开后第一帧是 `authenticate`:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"type": "authenticate",
|
|
166
|
+
"protocol_version": 1,
|
|
167
|
+
"access_token": "<HR-SaaS JWT>",
|
|
168
|
+
"requested_channels": ["ai_employee"],
|
|
169
|
+
"cursors": {}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
4. 收到 `realtime_ready` 且 `channels.ai_employee.available` 为 `true` 后,才能订阅当前 session 并启动心跳。
|
|
174
|
+
5. 用户输入始终通过宿主 transport 的 HTTP 能力发送,不通过 WebSocket 发送。
|
|
175
|
+
6. `4401` 仅表示 JWT 无效或过期,需要重新调用 `getAccessToken()` 并退避重连;`4400` 和 `4403` 是不可自动恢复的协议或权限错误。`1013` 以及认证帧中的 `SERVICE_*`、`KANBAN_AUTH_FAILED`、`REALTIME_UNAVAILABLE` 表示服务暂不可用,最多退避重连 3 次,不能误报为凭证失效。
|
|
176
|
+
7. 重连前优先通过 HTTP 恢复历史;历史恢复失败时才请求 WebSocket replay 兜底。
|
|
177
|
+
8. 实时消息必须按稳定 ID 去重,并忽略其他 session 的事件。
|
|
178
|
+
9. 主动断开和销毁后不得遗留 socket、心跳、重连任务或事件监听。
|
|
179
|
+
10. 未指定既有 `sessionId` 时,打开抽屉和点击“开启新对话”只能建立本地草稿,不得创建后端空 Session。首条消息必须通过 `createSessionWithFirstMessage()` 原子提交 `request_id + ae_code + agent_code + content + trusted_context + attachments`;失败重试同一操作必须复用原 `request_id`,成功后才绑定服务端返回的 Session 并订阅实时通道。已有 Session 的后续消息继续使用 `sendMessage(sessionId, content)`。
|
|
180
|
+
|
|
181
|
+
`ai_employee` channel、`session_message`、`session_title_updated`、订阅帧字段以及关闭码都是后端契约,不要仅为前端命名一致性自行修改。
|
|
182
|
+
|
|
183
|
+
人工客服使用同一个 `/api/user-realtime/ws`,但必须单独请求并订阅 `customer_support` channel;不得把客服会话并入 `ai_employee` 状态机。客服 HTTP transport 复用 HR-SaaS `customer_support_api` 的 `user_session_page`、`user_session_create`、`user_message_page`、`user_message_send` 和 `user_mark_read`。首条用户消息必须通过 `user_session_create.initial_message` 原子创建客服会话,不得先创建空会话再发送。点击“转人工”应释放数字员工连接并在同一抽屉切换到 `<uniplat-customer-support-chat>`;不得再向数字员工发送模拟转人工文本,也不得要求 Host 拼装第二个抽屉。未显式配置 `customerSupport` 时,数字员工组件必须复用自身的 `realtimeUrl`、`transport.getAccessToken()` 与同源 `/api/general/project/hr_saas/service/customer_support_api`;只有非标准网关或独立鉴权场景才要求 Host 覆盖。
|
|
184
|
+
|
|
185
|
+
## 安全要求
|
|
186
|
+
|
|
187
|
+
以下规则不可放宽:
|
|
188
|
+
|
|
189
|
+
- JWT 只允许出现在宿主系统的 token 获取或保存过程、WebSocket `authenticate` 首帧、Application Switcher 组织、入口和 ticket 签发请求的 `Authorization` header、Account Context 投影查询与组织切换请求的 `Authorization` header、Organization Onboarding 创建请求的 `Authorization` header、目标页面 ticket 兑换响应到其 Host `setAccessToken()` 的短暂传递过程,以及 User Login 登录响应到宿主 `setAccessToken()` 的短暂传递过程。
|
|
190
|
+
- SDK 不自动读取、保存或刷新长期 JWT;每次连接通过注入的 `transport.getAccessToken()` 获取当前有效令牌。
|
|
191
|
+
- JWT 不得进入 URL、HTML attribute、DOM 文本、CustomEvent、localStorage、日志、异常消息、测试快照或构建时配置。
|
|
192
|
+
- Application Switcher 的短期 ticket 只允许进入目标 URL fragment 和兑换请求体;必须是不透明、一次性且最多 60 秒的 URL-safe 值。ticket URL 只能作为函数局部导航值,不得进入 HTML attribute、DOM、组件响应式状态、CustomEvent、日志、异常、快照或构建配置;目标 helper 必须先清除 fragment 再兑换。
|
|
193
|
+
- 如果宿主使用内存状态、sessionStorage 或 HttpOnly Cookie,读取细节必须留在宿主提供的 `getAccessToken()` 中;SDK 不硬编码存储 key。
|
|
194
|
+
- 不得向浏览器包加入 HR-SaaS 或 Kanban 的 HMAC 密钥、内部签名逻辑。
|
|
195
|
+
- 不得信任或提交调用方给出的 `org_id`、`user_id` 或角色声明;身份以服务端 JWT 鉴权结果为准。
|
|
196
|
+
- 不向 UI 或公共事件透传认证响应、后端堆栈或原始错误正文。新增错误应映射为稳定错误码和安全提示。
|
|
197
|
+
- 示例和测试中的 token 只能使用明显的虚假值,构建产物中不得出现这些测试 token。
|
|
198
|
+
|
|
199
|
+
任何涉及认证、URL、错误传播或事件 detail 的修改,都要检查 URL、DOM、事件、日志和 `dist` 是否可能泄露凭据。
|
|
200
|
+
|
|
201
|
+
## Web Component 公开契约
|
|
202
|
+
|
|
203
|
+
### 配置
|
|
204
|
+
|
|
205
|
+
聊天组件通过 JavaScript property 方法配置:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
chat.configure({
|
|
209
|
+
realtimeUrl,
|
|
210
|
+
aeCode,
|
|
211
|
+
agentCode,
|
|
212
|
+
sessionId,
|
|
213
|
+
transport,
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
chat.open(initialMessage?)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`initialMessage` 是可选字符串;非空时由组件在初始化成功后通过既有消息发送链路自动发送:未配置 `sessionId` 时使用 `createSessionWithFirstMessage()` 原子创建会话并提交首条消息,已配置 `sessionId` 时使用 `sendMessage()` 作为该会话的后续消息。Host 不得代为创建会话或操作组件私有输入状态。未传、空字符串或纯空白时只打开抽屉,保持原有行为。
|
|
220
|
+
|
|
221
|
+
不要把对象、回调或 token 改为 HTML attribute。`sessionId` 可选;传入时直接绑定已有会话,不传时组件只建立本地草稿。transport 必须提供 `createSessionWithFirstMessage()`,由 SDK 生成 UUID `request_id` 并在首条消息时一次性提交创建会话所需的数据;Host 只负责协议字段映射,不得先调用空 Session 创建接口。网络失败、组件重建或同一标签页刷新后,用户重试未改变的同一首条消息时复用原 `request_id`;默认恢复存储只允许包含 UUID 和输入摘要,不得保存消息正文、附件内容、JWT 或后端响应。成功响应中的 `sessionId/messageId/executionId/aeCode/agentCode/conversationType` 是唯一权威标识。`getOrCreateSession()/createSession()` 已从公开 transport 删除,不再保留旧空会话兼容分支。聊天组件初始关闭,Host 只负责配置并调用 `open()` 激活;固定右侧抽屉、标题、关闭按钮、消息区、输入区和转人工入口均由组件自身渲染,关闭时组件自行调用 `close()` 并释放实时连接。Host 不得再包裹重复抽屉或拼装抽屉内部界面。标题左侧会话入口由组件内部切换到原型“我的会话”视图;真实列表复用 transport 的可选 `listSessions()`,选择后由组件调用 core `bindSession()`,可选 `markSessionRead()` 更新已读状态。当前选定会话始终视为已读,在聊天界面和“我的会话”列表中都不显示未读角标,也不计入标题入口的聚合未读数;历史加载完成、列表加载完成、当前会话收到实时消息以及从会话列表返回时,组件应立即清除该会话本地未读并异步同步 Host,失败后在下一次触发时重试。历史消息与列表响应使用不同消息 ID 时,以列表返回的 `latestMessageId` 再次同步,不得让当前会话重新出现未读角标;其他未选定会话的新消息仍应计为未读。
|
|
222
|
+
|
|
223
|
+
数字员工的 `assistant` 回复必须通过共享的安全 Markdown 渲染器展示;用户、系统和错误消息保持普通文本。Markdown 只能生成白名单结构,原始 HTML 必须作为文本转义,链接只允许 HTTP(S) 和 `mailto:`,不得直接注入未净化 HTML。
|
|
224
|
+
|
|
225
|
+
工作台内置应用切换通过 Account Context 的 `workbench` 配置:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
accountContext.configure({
|
|
229
|
+
applicationName,
|
|
230
|
+
transport: accountTransport,
|
|
231
|
+
workbench: { getAccessToken, transport },
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
人工客服组件通过 JavaScript property 配置:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
customerSupportChat.configure({
|
|
239
|
+
realtimeUrl,
|
|
240
|
+
transport,
|
|
241
|
+
sessionId,
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`transport` 负责客服会话列表、首条消息原子创建、历史消息、后续发送、已读同步与 `getAccessToken()`;真实 HR-SaaS 对接使用 `createHrSaasCustomerSupportTransport()`。组件初始关闭,由 Host 或数字员工组件调用 `open()`。组件自身拥有完整抽屉、会话列表、消息区、输入区和关闭行为。公开事件为 `ready`、`message-sent`、`message-received`、`connection-state-change`、`closed` 与 `error`,不得包含 JWT、transport 或后端原始异常。
|
|
246
|
+
|
|
247
|
+
通知中心通过 JavaScript property 配置:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
notificationCenter.configure({
|
|
251
|
+
applicationName,
|
|
252
|
+
transport,
|
|
253
|
+
pageSize,
|
|
254
|
+
preferredTypeOrder,
|
|
255
|
+
typeIcons,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
notificationCenter.applyRealtimeEvent(rawEvent)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
小型通知浮层复用相同 transport:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
notificationPopover.configure({ applicationName, transport, typeIcons })
|
|
265
|
+
notificationPopover.open()
|
|
266
|
+
notificationPopover.applyRealtimeEvent(rawEvent)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`configure()` 不接收实时地址、socket 工厂或重连参数。`applicationName` 必填并透传到 transport 请求体,不从 URL 或 JWT 推断。transport 提供 `listInbox()`、`getUnreadCount()`、`markRead()`、`markAllRead()`、`downloadAttachment()`,可选 `archive()` 与 `parseRealtimeEvent()`;它不暴露 `getAccessToken()`,JWT 只在 transport 内部 HTTP 请求的 `Authorization: Bearer` header 中使用。真实 UniplatBase 对接使用 `createUniplatBaseNotificationCenterTransport()`,复用 `messaging.notification_api` 与统一文件系统下载 ticket。`applyRealtimeEvent()` 接收 Host socket 的 `realtime_event.event` 原始负载,由 transport 的 `parseRealtimeEvent()` 映射为增量变更;无法映射时降级为整页刷新,收件箱请求在途时延后为 resync。分类页签与列表里的分类名必须由数据决定,SDK 不得保存 code 到中文名的映射:展示名一律取后端 `notification_type_name`(DTO 的 `typeName`),为空时回退到原始 `notification_type`。分类目录由 `listInbox({ includeTypeFacets: true })` 随收件箱同一请求取回的 `types` 提供(对应后端 `include_type_facets` 与 `type_facets`),不额外发请求;后端未返回时回退为累积已加载页面出现过的分类,且按分类筛选后目录不得塌缩。顺序默认照搬后端目录,`preferredTypeOrder` 只用于 Host 显式置顶少量 code,SDK 默认不 privilege 任何 code。`TYPE_META` 只保留原型的图标与色底,不含展示名。公开事件为 `ready`、`unread-count-change`、`notification-read`、`notification-resource-requested` 与 `error`,不含 `connection-state-change`,detail 不得包含 JWT、下载 ticket、transport 或后端原始异常。
|
|
270
|
+
|
|
271
|
+
Popover 使用 `listInbox({ pageSize: 4, readStatus: 'unread' })`,只渲染未读结果;顶部总数仍由 `getUnreadCount()` 提供,不能把首屏条数误作总数。点击单条先调用 `markRead()`,随后关闭并派发 `notification-center-requested({ notificationId })`;底部入口派发空 detail,全部已读调用 `markAllRead()`。它另外派发 `closed`,其余 `ready`、`unread-count-change`、`notification-read` 与 `error` 语义和完整通知中心一致。
|
|
272
|
+
|
|
273
|
+
通知中心与 Popover 的 `applicationName` 配置项必须存在:非空值表示单应用投影,空字符串表示当前用户、当前组织下的全应用聚合投影。全应用模式下 UniplatBase transport 对 `inbox/unread_count/mark_read/mark_all_read/archive` 都不发送 `application_name`,不能将 JWT 当前应用或其他默认值补回请求。
|
|
274
|
+
|
|
275
|
+
内嵌工作台不提供组织选择;应用身份切换通过 transport 的可选 `switchApplication()` 提供,该方法可以返回只供导航使用的 `{ url }`,但该 URL 不得进入 DOM、组件状态或公共事件。不实现该方法的 transport 仍可只显示应用入口。所有应用必须在新窗口打开,不得覆盖 Host 当前页面;需要异步签发 ticket 时,应在用户点击的同步阶段预先打开无凭据空白窗口,失败或事件被取消时关闭该窗口。真实 HTTP transport 必须把 JWT 放在 `Authorization: Bearer` header,不得拼入 endpoint、入口 URL、空白窗口或切换请求体。组件不得长期保存 JWT,并应在每次加载或应用切换时重新调用 `getAccessToken()`。
|
|
276
|
+
|
|
277
|
+
对接 UniplatBase 时使用 `createUniplatBaseApplicationSwitcherTransport()`,复用 `application.identity/list_available_applications`,并扩展 `switch_current_application` 的 `response_type=ticket` 模式。内嵌工作台只加载当前身份可展示的应用入口;`list` 中通过安全校验且 `oid` 等于当前组织或 `application_scope=open` 的应用必须按返回顺序显示,其他组织的应用不显示,符合条件的应用不能截断为前 9 个。点击应用时签发 15 秒一次性 ticket,并以 `#uniplat_application_ticket=` 传到新窗口;当前 Host JWT 不更新。目标页面使用 `exchangeUniplatBaseApplicationTicket()` 调用 `anonymous/application.identity/exchange_application_ticket`,必须先清除 fragment,再把兑换 JWT 交给其 Host `setAccessToken()`。ticket 和 JWT 均不得进入 DOM、公共事件、日志或异常;该 ticket 与 Kanban WebSocket 协议无关。
|
|
278
|
+
|
|
279
|
+
User Login 同样通过 JavaScript property 配置:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
userLogin.configure({
|
|
283
|
+
transport,
|
|
284
|
+
applicationName,
|
|
285
|
+
headerTitle,
|
|
286
|
+
headerSubtitle,
|
|
287
|
+
headerIconUrl,
|
|
288
|
+
securityHint,
|
|
289
|
+
userAgreementUrl,
|
|
290
|
+
privacyAgreementUrl,
|
|
291
|
+
forgotPasswordUrl,
|
|
292
|
+
initialMethod,
|
|
293
|
+
})
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`applicationName` 必须由 Host 通过组件配置提供,组件登录时将其透传给 transport;不得从 URL、JWT 或组件内部默认值推断。顶部标题、副标题、图标和底部安全提示属于 User Login 组件内部布局,但内容由 Host 通过上述配置提供;右上角关闭按钮和弹窗开关仍属于 Host。所有文案使用普通文本插值,图标只接受不含认证参数的 HTTP(S) URL 或图片 Data URL,不允许 Host 传入可执行 HTML。
|
|
297
|
+
|
|
298
|
+
对接 UniplatBase 时使用 `createUniplatBaseUserLoginTransport()`,复用 `anonymous/system.user/sendVerifyCode` 和 `anonymous/application.identity/login-application`,不得为组件新建平行登录接口。短信请求只发送手机号,不发送图形验证码 seed 或 verifycode;`login-application` 使用 `auth_method=sms|password|qr`,扫码创建和轮询使用 `action=create|poll`。`is_new_user` 决定是否进入注册成功过渡页,字段缺失时按既有用户兼容;错误只按白名单 `error_code` 映射,不读取或透传后端 `msg`。自定义 transport 的密码登录使用可选 `loginWithPassword()`,扫码登录同时使用可选 `createQrChallenge()` 和 `pollQrChallenge()`;专用 UniplatBase transport 必须完整实现这三个方法,并优先采用后端返回的二维码轮询间隔。二维码 challenge ID 只能保存在普通私有字段和 transport 调用参数中,不能进入 Lit 响应式状态、DOM、事件、日志或错误;二维码图片不得通过 query/hash 携带 challenge 或认证参数,也不得在 URL 中重复原始 challenge ID。确认、拒绝、过期、切换页签、重新配置或移除组件时必须停止扫码轮询和注册成功倒计时。专用 transport 必须在返回成功前等待宿主 `setAccessToken()`;传给其第二参数的元数据必须移除 JWT。组件和 transport 不读取或写入宿主存储;密码只允许存在于密码输入框的 value property、组件短期私有状态和 transport 调用参数中,切换离开密码页签、登录成功或移除组件时必须清空,且不得进入 HTML attribute、DOM 文本、公共事件、URL、日志或错误。JWT、认证响应和后端原始异常同样不得通过这些渠道暴露。登录请求的 `device_id` 使用稳定的浏览器设备指纹(Host 可通过 `deviceId` 覆盖,优先级更高),不得每次随机生成,SDK 也不持久化该标识。后端契约以 `/Users/gaostudio/.vibe-requirements/UniplatBase_应用登录密码验证码与扫码接口文档_v0_1.md` 为准。
|
|
299
|
+
|
|
300
|
+
User Menu 同样通过 JavaScript property 配置:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
userMenu.configure({
|
|
304
|
+
displayName,
|
|
305
|
+
maskedMobile,
|
|
306
|
+
avatarText,
|
|
307
|
+
avatarUrl,
|
|
308
|
+
currentOrganizationId,
|
|
309
|
+
organizationCount,
|
|
310
|
+
organizations,
|
|
311
|
+
})
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`maskedMobile` 只能是脱敏展示值,不能传完整手机号。`organizations` 只包含稳定 ID、团队名称、`company | team` 类型、可选角色与成员数;不得把 JWT、完整身份响应、transport 或内部权限对象交给组件。组件不得自行改变当前组织,Host 必须在 `organization-selected` 对应的切换成功后用新的 `currentOrganizationId` 重新配置。企业/团队列表要完整显示,超出固定高度时只让列表区滚动,不能截断数据或改变面板宽度。
|
|
315
|
+
|
|
316
|
+
组织入驻通过 Account Context 的 JavaScript property 配置:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
accountContext.configure({
|
|
320
|
+
applicationName,
|
|
321
|
+
organizationOnboarding: { transport },
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
transport 只提供 `createEnterprise()` 和 `createTeam()`;稳定冲突错误使用 `OrganizationOnboardingTransportError`,不得把原始响应、JWT 或内部异常透传给组件。`createEnterprise()` 接收组织名称、企业名称与统一社会信用代码,`createTeam()` 只接收团队名称。创建成功响应只包含稳定 ID、展示名称和 `enterprise | team` 类型。
|
|
326
|
+
|
|
327
|
+
真实 UniplatBase 对接使用 `createUniplatBaseOrganizationOnboardingTransport({ serviceBaseUrl, getAccessToken })`,复用 `system.org/create_organization`。每次创建前必须重新取 JWT,并只放入 `Authorization: Bearer` header;请求不得携带调用方身份字段。只对白名单冲突码做字段级映射,`INVALID_ARGUMENT`、`ORGANIZATION_CREATE_FAILED` 和所有非协议异常统一收敛为 `UNKNOWN`。
|
|
328
|
+
|
|
329
|
+
Organization Selector 的展示数据仍由 Host 通过 `configure()` 提供;它不接收 JWT,`organization-enter-requested` 只包含组织 ID 和记住偏好。Account Context 必须显式接收 `applicationName` 和账户 transport,由组件调用应用组织投影接口;用户资料复用 `list_available_organizations` 顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得调用 `profile_summary_v2`。Host 只提供 transport 内的 `getAccessToken()/setAccessToken()`,不得再提供姓名、头像、当前组织、可选组织或认证状态。组织选择事件仍由 Host 响应,Host 可调用组件的 `switchOrganization()` 完成切换,组件自行保存投影并刷新展示。账户 transport 与 `workbench` 的 Application Switcher transport 职责不同:前者只负责账户、组织与认证上下文,后者只负责工作台应用入口和短期 ticket。有当前组织时 `showWorkbench` 默认开启,Host 可显式关闭。头像 URL 只能是无凭据、无 fragment、无认证参数的 HTTP(S) 地址,失败时回退文字头像;专用 transport 可将严格匹配且员工 ID 一致的历史受管头像路径转换为同一服务下的匿名头像接口,不得把内部存储路径写入 DOM。个人态使用无外框头像姓名。应用名、所有显示文本和头像 URL 均不得进入公共事件。旧的 `displayName/organizations` 等手工展示配置只为兼容保留,不再用于新接入。
|
|
330
|
+
|
|
331
|
+
### 事件
|
|
332
|
+
|
|
333
|
+
聊天组件当前派发:
|
|
334
|
+
|
|
335
|
+
```text
|
|
336
|
+
ready
|
|
337
|
+
message-sent
|
|
338
|
+
message-received
|
|
339
|
+
connection-state-change
|
|
340
|
+
error
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
User Login 当前派发:
|
|
344
|
+
|
|
345
|
+
```text
|
|
346
|
+
sms-sent
|
|
347
|
+
login-success
|
|
348
|
+
onboarding-requested
|
|
349
|
+
agreement-requested
|
|
350
|
+
forgot-password-requested
|
|
351
|
+
error
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
User Menu 当前派发:
|
|
355
|
+
|
|
356
|
+
```text
|
|
357
|
+
account-settings-requested
|
|
358
|
+
employee-info-requested
|
|
359
|
+
organization-selected
|
|
360
|
+
organization-create-requested
|
|
361
|
+
logout-requested
|
|
362
|
+
error
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
内部组织入驻经 Account Context 派发:
|
|
366
|
+
|
|
367
|
+
```text
|
|
368
|
+
onboarding-skipped
|
|
369
|
+
join-organization-requested
|
|
370
|
+
organization-created
|
|
371
|
+
enterprise-authentication-requested
|
|
372
|
+
enterprise-authentication-deferred
|
|
373
|
+
team-upgrade-requested
|
|
374
|
+
team-invite-members-requested
|
|
375
|
+
error
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Organization Selector 当前派发:
|
|
379
|
+
|
|
380
|
+
```text
|
|
381
|
+
organization-enter-requested
|
|
382
|
+
organization-create-requested
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Account Context 当前派发:
|
|
386
|
+
|
|
387
|
+
```text
|
|
388
|
+
account-menu-requested
|
|
389
|
+
account-settings-requested
|
|
390
|
+
employee-info-requested
|
|
391
|
+
workbench-requested
|
|
392
|
+
enterprise-authentication-requested
|
|
393
|
+
organization-selected
|
|
394
|
+
logout-requested
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Notification Popover 当前派发:
|
|
398
|
+
|
|
399
|
+
```text
|
|
400
|
+
ready
|
|
401
|
+
unread-count-change
|
|
402
|
+
notification-read
|
|
403
|
+
notification-center-requested
|
|
404
|
+
closed
|
|
405
|
+
error
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
公开事件必须使用标准 `CustomEvent`,并保持 `bubbles: true`、`composed: true`。事件 detail 只能包含业务数据、状态或净化后的错误,不能包含 transport、JWT 或内部异常对象。
|
|
409
|
+
|
|
410
|
+
### 样式
|
|
411
|
+
|
|
412
|
+
- 默认样式放在 Shadow DOM 内。
|
|
413
|
+
- 宿主定制通过少量 CSS Custom Properties 和 `::part` 完成。
|
|
414
|
+
- 原型明确使用的图片、图标或图标版本必须复用原始资源,不得用 emoji、近似 SVG、其他图标库或自行重绘图形替代。40.1 原型使用的 Tabler Icons 固定为 2.47.0;Web Component 内使用其原始 SVG 路径,以保证 Shadow DOM 和独立浏览器运行时不依赖 Host 图标字体。原型中的组织简称色块属于文字内容,不得误改成楼宇、人员等替代图标。
|
|
415
|
+
- 新增视觉公开点时同步 README;不要依赖宿主 UI 框架或全局 reset。
|
|
416
|
+
- 当前目标浏览器为主流 Chromium、Safari 和 Firefox,新增 API 时注意兼容性。
|
|
417
|
+
|
|
418
|
+
### 兼容性
|
|
419
|
+
|
|
420
|
+
npm 包名、导出类型名、Custom Element 标签、`configure()` 结构、事件名和 CSS 公共变量都视为公开 API。破坏性变更必须由用户明确授权,并提供迁移说明及相应测试;不能顺手重命名。
|
|
421
|
+
|
|
422
|
+
## 测试约定
|
|
423
|
+
|
|
424
|
+
- 协议解析、认证首帧、订阅顺序、心跳、JWT 到期重连、消息去重和销毁行为放在 core 测试中。
|
|
425
|
+
- 首条消息原子建会话需测试:打开和新建仅产生本地草稿、首条请求字段映射、失败重试复用 `request_id`、成功后使用服务端稳定 ID 绑定与订阅、HTTP 与 WebSocket 竞态去重、已有会话仍走后续消息接口,以及请求 ID、执行 ID 和认证信息不进入 DOM、公共事件或日志。
|
|
426
|
+
- Web Component 注册、Shadow DOM 输出、事件、安全 detail 和断开释放放在 happy-dom 测试中。
|
|
427
|
+
- 新增 Web Component 至少测试:标签已注册、类已导出、关键首版输出正确。
|
|
428
|
+
- Account Context 内嵌工作台需测试不注册独立应用切换标签、JWT 只交给 transport、HTTP Authorization header、入口净化、当前组织级应用加 `application_scope=open` 应用的筛选、缺字段时 `oid=0` 兼容、Open Application ticket 的 `organization_oid`、公共事件无 JWT/ticket、ticket 只进入局部导航 URL、目标页面先清 fragment 再兑换、加载错误、仅请求应用列表和应用选择交互。
|
|
429
|
+
- User Login 需测试组件注册与导出、三个固定页签、不可用能力禁用、原型中不存在图形验证码且不调用兼容方法、逐字段失焦校验和协议错误态、稳定后端错误码映射、短信倒计时、密码登录、扫码待确认/已扫码/过期/成功状态、注册成功过渡页、2 秒自动入驻和立即开始、页签切换与断开后的定时器清理、登录回调,以及 JWT、密码和二维码 challenge 不进入 URL、DOM、公共事件、日志或安全错误。
|
|
430
|
+
- User Menu 需测试组件注册与导出、40.1 6.7 原型关键内容、个人账号与当前组织员工信息入口、企业认证与团队样式、安全头像与文字回退、Host 受控选中状态、重新配置、空组织状态、完整手机号拒绝、创建与退出事件、普通文本插值,以及公共事件只包含必要的组织 ID,不包含显示名、手机号、组织名称、头像 URL、JWT 或内部配置。
|
|
431
|
+
- 内置组织入驻需测试所有原型步骤、逐字段校验、稳定 transport 错误映射、企业/团队成功状态、跳过与未展开流程事件、普通文本插值,以及公共事件不包含企业信息、信用代码、JWT 或原始异常。真实 UniplatBase transport 还必须测试每次请求重新取 JWT、JWT 只进入 Authorization header、企业/团队请求字段、成功响应净化和未知错误收敛。
|
|
432
|
+
- Organization Selector 需测试完整列表、企业认证与团队类型样式、默认选中、记住偏好、创建与进入事件、重复 ID 拒绝和普通文本插值;事件只包含稳定组织 ID 与偏好。
|
|
433
|
+
- Account Context 需测试必填应用名、组件通过 transport 自行加载用户资料与完整应用组织投影、无归属、企业五种认证状态和团队状态、当前组织员工身份优先及无组织用户身份回退、安全头像与加载失败回退、工作台、账户菜单与内置入驻浮层互斥、浮层不改变导航布局、内嵌应用九宫格、创建入口直接启动内置入驻、Host 响应组织切换事件、退出和公共事件安全。专用 UniplatBase transport 还必须测试不请求 `profile_summary_v2`、从 `list_available_organizations` 顶层读取用户资料、按 `application_name` 加载投影、Open Application 的 `organization_oid` 切换、组织级应用目标 `xid` 选择、JWT 只进入 Authorization header 和 Host `setAccessToken()`、响应净化、稳定错误以及公共数据无 JWT;组件范围不得扩展为品牌或完整导航。
|
|
434
|
+
- 通知中心需测试组件注册与导出、两栏首版输出、数据驱动分类页签(`includeTypeFacets` 随收件箱请求、后端 `type_facets` 优先、无 facet 回退累积、筛选后页签不塌缩、展示名取 `notification_type_name` 且为空时回退原始 code、契约四个码不得残留内置中文名、实时新分类入目录)、未读角标与 `unread-count-change`、打开未读即标记已读、全部已读、加载更多、关联资源事件只含安全业务标识、附件经 transport 下载、Markdown 净化、`applyRealtimeEvent()` 不建立任何 WebSocket 且原地更新列表与未读数、事件无法映射时降级刷新、收件箱在途时延后 resync 不丢事件、`configure()` 之前与移除之后喂入事件无副作用、错误横幅为净化文案,以及 JWT 与后端原始正文不进入 DOM、公共事件或日志。小型通知浮层还需测试初始关闭、4 条未读投影、未读总数、严格原型结构与 SVG 图标、单条/全部已读、完整通知页意图事件只含稳定 ID、外部关闭、空态以及 Host 实时事件更新。
|
|
435
|
+
- 使用 fake timer 的测试必须恢复 timer;构造 fake WebSocket 时不要把测试凭据写入 URL。
|
|
436
|
+
- 修复竞态或生命周期问题时增加回归测试,不只验证最终 DOM。
|
|
437
|
+
- 不以删除或弱化测试的方式绕过失败。
|
|
438
|
+
|
|
439
|
+
## TypeScript 与代码风格
|
|
440
|
+
|
|
441
|
+
- 保持 `strict`、`noUncheckedIndexedAccess`、`noImplicitOverride`、`useUnknownInCatchVariables` 和 `isolatedModules`。
|
|
442
|
+
- 类型导入使用 `import type`;ESLint 禁止显式 `any`。
|
|
443
|
+
- 面向外部输入使用 `unknown` 并在边界收窄,不直接断言为可信协议类型。
|
|
444
|
+
- 优先使用小而明确的类型和函数,不为未确认的文件、语音、Markdown、客服或多会话能力预设复杂抽象。
|
|
445
|
+
- 不记录原始 WebSocket 帧、transport 响应、token provider 异常或可能包含凭据的对象。
|
|
446
|
+
- 保持现有无分号、单引号和 Prettier 格式。
|
|
447
|
+
|
|
448
|
+
## 构建产物与依赖
|
|
449
|
+
|
|
450
|
+
- `dist/` 是生成目录,不手工编辑;修改源码后用 `pnpm build` 重新生成。
|
|
451
|
+
- 每次代码修改后的最终交付必须重新生成 core 和 web-components 两个 tarball;不得用修改前的包代替本次源码构建结果,也不得只更新源码或 `dist/` 而遗漏打包。
|
|
452
|
+
- core 和 web-components 发布包必须继续包含 ESM、`.d.ts` 和 source map。
|
|
453
|
+
- Web Components 构建还必须生成 `dist/runtime/current.json` 与 `dist/runtime/releases/<version>/index.js` 独立浏览器 ESM。该模块必须完整打包 `lit` 和 core、不得保留 bare import;清单 schema/API version 当前均为 `1`,模块路径必须包含包版本。发布时先上传不可变版本目录再原子切换无缓存清单,确保 qqxbwebsite 无需重新构建即可升级;具体契约以 `/Users/gaostudio/.vibe-requirements/qqxbwebsite_UniBase运行时自动升级需求_v0_1.md` 为准。
|
|
454
|
+
- workspace 内部依赖使用 `workspace:*`。
|
|
455
|
+
- 引入运行时依赖前先判断能否用平台能力实现,尤其不要给 core 增加 UI 或框架依赖。
|
|
456
|
+
- 修改导出、包名或构建入口时检查 `package.json`、TypeScript paths、Vitest alias、tsup external、示例 import 和 lockfile 是否同步。
|
|
457
|
+
|
|
458
|
+
## 需求与相关实现
|
|
459
|
+
|
|
460
|
+
- 所有关联项目的职责与代码路径统一登记在 `/Users/gaostudio/.vibe-requirements/projects.md`。查阅跨项目文档或代码前应先读取该清单,并以清单中的路径为准;如果当前负责的项目不在清单中,应按现有表格格式补充项目名称、主要职责和所在路径。
|
|
461
|
+
- 提出跨项目接口需求前,必须先核对提供方的现有文档、实现和调用方。优先复用语义相符的已有接口,并优先通过增加可选入参或新增响应字段向后兼容地扩展;只有在鉴权边界、事务语义、性能或职责归属确实无法复用时才新增接口,不能为单个前端组件盲目建立平行 API。
|
|
462
|
+
- 本项目当前基础需求:`/Users/gaostudio/.vibe-requirements/uni-base_基础建设需求_v0_1.md`。
|
|
463
|
+
- AI Employee Chat 完整右侧抽屉原型:`/Users/gaostudio/ae_design/1、新平台底座搭建/1、亲亲企服平台(基础能力+企服客户端)/3、需求及UI设计/官网及企服客户端设计/40、亲亲企服官网.html#consult`。组件负责原型中的完整 420px 固定右侧抽屉,Host 只负责调用 `open()` 激活,不得重复渲染抽屉标题、消息容器或底部操作区。
|
|
464
|
+
- 用户首条消息原子创建会话联合需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_KanbanBackend_AE-Enterprise-Admin_QQXBWebsite_UniBase_HR-SaaS_用户首条消息原子创建会话联合需求_v0_1.md`。UniBase 负责本地草稿、稳定 `request_id`、原子 transport、权威 ID 绑定及实时竞态去重;不得在打开抽屉或新建对话时创建空 Session。
|
|
465
|
+
- User Login、内置组织入驻、Organization Selector、Account Context 与 User Menu 的界面和状态总原型:`/Users/gaostudio/qqxbwebsite/原型/40.1、注册登录与企业入驻流程-standalone.html`。修改任一能力前必须直接核对该文件。User Login 覆盖 01 和 03;内置组织入驻覆盖 02、A/B 分支与 4.1;Organization Selector 覆盖 5.1;Account Context 覆盖 4.2、5.2、06、6.7 以及工作台应用入口,User Menu 是其中 6.7 的独立兼容入口。原型展示但未展开页面的邀请、认证、升级和邀请成员操作只派发事件,由 Host 实现页面流程。
|
|
466
|
+
- User Login 注册判定与稳定错误码接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_UserLogin注册结果与稳定错误码需求_v0_1.md`。
|
|
467
|
+
- 企业团队入驻与归属展示接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_企业团队入驻与归属展示接口需求_v0_1.md`;UniplatBase 已反馈的接口说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/企业团队入驻与归属展示接口说明_v0_1.md`。对接时必须核对 `services/system/org.groovy`、统一组织分类工具和 `tests/verify_organization_onboarding_api.py`,不能只按示例响应实现。
|
|
468
|
+
- 账户上下文按组织显示员工姓名与头像的接口字段扩展需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_账户上下文组织员工身份展示字段需求_v0_1.md`。复用 `application.identity/list_available_organizations.organizations[]` 的员工展示字段;用户资料复用该接口顶层的 `display_name`、`avatar_url` 与 `mobile_masked`,不得调用 `system.user/info`、`profile_summary_v2` 或新增平行身份接口。
|
|
469
|
+
- Account Context 按当前应用筛选组织并安全切换全局应用组织上下文的接口需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_AccountContext按应用筛选与组织切换需求_v0_1.md`;UniplatBase 已反馈说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/AccountContext按应用筛选与组织切换接口说明_v0_1.md`。继续复用 `list_available_applications` 与 `switch_current_application`;工作台内嵌应用面板不得请求或显示组织列表,也不得把后端切换目标传入组件配置或公共事件。工作台必须优先读取 `application_scope`,只在旧响应缺失该字段时以 `oid=0` 临时兼容 Open Application。
|
|
470
|
+
- 旧 `/Users/gaostudio/qqxbwebsite/原型/40、亲亲企服官网.html#consult` 仅用于理解历史 User Menu 调用,不再作为当前菜单视觉依据;当前视觉以 40.1 的 6.7 为准。
|
|
471
|
+
- 通知中心 Web 组件动态接入需求:`/Users/gaostudio/.vibe-requirements/UniBase_AE-Enterprise-Admin_通知中心Web组件动态接入需求_v0_1.md`(v0.2 修订:实时连接归 Host,组件只经 `applyRealtimeEvent()` 消费事件)。视觉原型为 `/Users/gaostudio/ae_design/2-数字人事部/产品设计/杜星霖工作目录/过程原型文件/数字人事部_管理端_HRD视角_v0_6.html`。后端投影与实时事件契约见同目录 `UniplatBase_KanbanBackend_通知中心真实数据与实时通知接口需求_v0_1.md` 与按应用身份过滤联合需求。
|
|
472
|
+
- 小型消息通知浮层视觉与交互原型:`/Users/gaostudio/ae_design/1-新平台底座搭建/1-亲亲企服平台(基础能力+企服客户端)/3-需求及UI设计/官网及企服客户端设计/40、亲亲企服官网.html`;只复用现有通知中心接口与 Host 实时通道,不另写平行后端需求。
|
|
473
|
+
- 通知中心空 `applicationName` 的全应用聚合后端需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_通知中心全应用聚合查询与操作需求_v0_1.md`。
|
|
474
|
+
- Application Switcher 对接 UniplatBase 的接口差异与扩展需求:`/Users/gaostudio/.vibe-requirements/UniplatBase_UniBase_ApplicationSwitcher接口适配需求_v0_1.md`。
|
|
475
|
+
- UniplatBase 已反馈的 Application Switcher 接口说明:`/Users/gaostudio/ae_design/实现/uniplat_base/docs/ApplicationSwitcher接口说明_v0_1.md`;对接时同时核对该项目实际实现和验证脚本,不能只按示例响应猜测。
|
|
476
|
+
- 新需求优先从 `/Users/gaostudio/.vibe-requirements` 查找目标项目同名文档;默认不在该目录创建子目录。
|
|
477
|
+
- 需要确认实时协议时,可以只读核对 Kanban 和 Enterprise Admin 的现有实现;未经用户明确要求,不修改其他项目。
|
|
478
|
+
- 需求与后端现状冲突时应明确指出差异,不静默选择兼容分支,也不要在 SDK 中预埋未经确认的抽象。
|
|
479
|
+
|
|
480
|
+
## 工作范围与 Git
|
|
481
|
+
|
|
482
|
+
- 保留用户已有改动,避免覆盖与当前任务无关的文件。
|
|
483
|
+
- 默认只改本仓库;跨项目变更应说明需要哪个项目配合。
|
|
484
|
+
- 新开发任务开始前,先检查上一项任务的修改;如果上一项任务已经完成且通过要求的验证,应主动将其提交,再开始新任务,避免不同任务混入同一提交。
|
|
485
|
+
- 未完成、未通过验证、无法确认来源或包含用户无关改动的内容不得擅自提交;不能为满足提交要求而删除、覆盖或顺带整理这些修改。
|
|
486
|
+
- 暂存时只选择当前已完成任务的明确文件,避免使用 `git add .`、`git add -A` 等可能混入无关修改的宽泛命令。
|
|
487
|
+
- 每次提交前必须在仓库根目录运行并通过:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
pnpm lint
|
|
491
|
+
pnpm typecheck
|
|
492
|
+
pnpm test
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
本仓库脚本名是 `typecheck`,不是 `type-check`。如果检查失败,应先修复或明确报告阻塞,不得提交失败结果。
|
|
496
|
+
|
|
497
|
+
- 暂存完成后、执行提交前必须依次检查:
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
git status --short
|
|
501
|
+
git diff --cached --stat
|
|
502
|
+
git diff --cached
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
需要确认暂存文件全部属于当前任务、变更范围符合预期、没有凭据、生成噪声或来源不明的内容。
|
|
506
|
+
|
|
507
|
+
- 一个提交只处理一件事;如果已完成修改包含多个相互独立的主题,应拆分提交并分别验证暂存内容。
|
|
508
|
+
- 提交信息使用中文,格式为:`<type>(<scope>): <完整说明>`。说明应准确表达已经完成的行为,不能使用“更新代码”“修复问题”等模糊表述。
|
|
509
|
+
- `type` 仅使用 `feat`、`fix`、`refactor`、`perf`、`docs`、`test`、`chore`。
|
|
510
|
+
- 推荐 scope:`core`、`web-components`、`example`、`build`、`docs`;可根据实际单一主题选用更准确且稳定的 scope。
|
|
511
|
+
- 破坏性变更必须在提交正文中明确写出影响范围、迁移方式和风险;不能只依赖标题或 `!` 标记表达。
|
|
512
|
+
- 不自行初始化 Git 仓库、切换分支或推送远端。除“新任务开始前提交上一项已完成且已验证的任务”外,其他提交仍需用户明确要求。
|