@csntgao/uni-base 0.6.2 → 0.6.5

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.
@@ -1,7 +1,9 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
3
  import { describe, expect, it, vi } from 'vitest'
4
- import { createHrSaasCustomerSupportTransport } from '../src'
4
+ import { createHrSaasCustomerSupportTransport, HrSaasCustomerSupportTransportError } from '../src'
5
+
6
+ const actionTransactionId = '8f0b790c-1c5f-4f8c-95cc-4ead9d9cfb1a'
5
7
 
6
8
  function response(data: unknown): Response {
7
9
  return new Response(JSON.stringify({ rescode: 0, data }), {
@@ -76,4 +78,90 @@ describe('createHrSaasCustomerSupportTransport', () => {
76
78
  expect(new Headers(call[1]?.headers).get('authorization')).toBe('Bearer customer.support.jwt')
77
79
  }
78
80
  })
81
+
82
+ it('creates an idempotent handoff with only the source session and trusted action id', async () => {
83
+ const fetchImplementation = vi.fn<typeof fetch>(async (input, init) => {
84
+ void input
85
+ void init
86
+ return response({
87
+ session: {
88
+ session_id: 'support-81',
89
+ title: '职责越界转接',
90
+ status: 'waiting',
91
+ unread_count: 0,
92
+ },
93
+ initial_message: {
94
+ message_id: 'support-message-91',
95
+ session_id: 'support-81',
96
+ sequence: '1',
97
+ sender_type: 'customer',
98
+ content: '来自数字员工会话的转接',
99
+ created_at: '2026-09-09T08:00:00Z',
100
+ },
101
+ created: true,
102
+ repaired: false,
103
+ })
104
+ })
105
+ const transport = createHrSaasCustomerSupportTransport({
106
+ serviceBaseUrl: 'https://hr.example.com/general/project/hr_saas/service/customer_support_api',
107
+ getAccessToken: () => 'customer.support.jwt',
108
+ fetch: fetchImplementation as typeof fetch,
109
+ })
110
+
111
+ const result = await transport.createHandoffSession?.({
112
+ sourceSessionId: 'ai-session-31',
113
+ actionTransactionId,
114
+ })
115
+
116
+ expect(result).toMatchObject({
117
+ session: { id: 'support-81', status: 'waiting' },
118
+ initialMessage: { id: 'support-message-91', sessionId: 'support-81' },
119
+ created: true,
120
+ repaired: false,
121
+ })
122
+ const [url, init] = fetchImplementation.mock.calls[0]!
123
+ expect(String(url)).toBe(
124
+ 'https://hr.example.com/general/project/hr_saas/service/customer_support_api/user_session_handoff_create',
125
+ )
126
+ expect(JSON.parse(String(init?.body))).toEqual({
127
+ source_session_id: 'ai-session-31',
128
+ action_transaction_id: actionTransactionId,
129
+ })
130
+ expect(String(init?.body)).not.toContain('ae_code')
131
+ expect(String(init?.body)).not.toContain('agent_code')
132
+ expect(String(init?.body)).not.toContain('org_id')
133
+ expect(new Headers(init?.headers).get('authorization')).toBe('Bearer customer.support.jwt')
134
+ })
135
+
136
+ it('maps handoff failures to stable safe errors without exposing backend text', async () => {
137
+ const fetchImplementation = vi.fn(
138
+ async () =>
139
+ new Response(
140
+ JSON.stringify({
141
+ rescode: 1,
142
+ error_code: 'HANDOFF_ACTION_MISMATCH',
143
+ msg: 'private upstream details',
144
+ }),
145
+ { status: 409, headers: { 'content-type': 'application/json' } },
146
+ ),
147
+ )
148
+ const transport = createHrSaasCustomerSupportTransport({
149
+ serviceBaseUrl: 'https://hr.example.com/general/project/hr_saas/service/customer_support_api',
150
+ getAccessToken: () => 'customer.support.jwt',
151
+ fetch: fetchImplementation as typeof fetch,
152
+ })
153
+
154
+ const request = transport.createHandoffSession?.({
155
+ sourceSessionId: 'ai-session-31',
156
+ actionTransactionId,
157
+ })
158
+
159
+ await expect(request).rejects.toMatchObject({
160
+ name: 'HrSaasCustomerSupportTransportError',
161
+ code: 'HANDOFF_ACTION_MISMATCH',
162
+ message: 'Customer support request failed',
163
+ })
164
+ await expect(request).rejects.not.toThrow('private upstream details')
165
+ expect(HrSaasCustomerSupportTransportError).toBeTypeOf('function')
166
+ })
79
167
  })
package/restart CHANGED
File without changes
@@ -6,7 +6,7 @@
6
6
  >
7
7
  > 分支:`main`
8
8
  >
9
- > 交接基线提交:`2aadb87 feat(web-components): 组织邀请页豁免强制入驻`
9
+ > 交接基线提交:`495ef35 fix(web-components): 创建组织后刷新账户菜单的组织列表`
10
10
 
11
11
  ## 1. Claude 接手后的第一步
12
12
 
@@ -25,7 +25,7 @@ git log -5 --oneline --decorate
25
25
  ```text
26
26
  main 与 origin/main 同步
27
27
  工作树无代码改动
28
- HEAD = 2aadb87
28
+ HEAD = 495ef35
29
29
  ```
30
30
 
31
31
  不要清理、覆盖或提交根目录已有的 `.tgz`。它们是历史及当前本地发布产物,已由 `.gitignore` 忽略。
@@ -38,9 +38,9 @@ HEAD = 2aadb87
38
38
 
39
39
  | 位置 | npm 名称 | 当前本地版本 | 说明 |
40
40
  | ------------------------- | -------------------------------------- | -----------: | --------------------------------------- |
41
- | 根目录 | `@csntgao/uni-base` | `0.4.0` | 当前公开发布入口;npmjs 已发布此版本 |
41
+ | 根目录 | `@csntgao/uni-base` | `0.6.2` | 当前公开发布入口;npmjs 已发布此版本 |
42
42
  | `packages/core` | `@uniplat/ai-employee-client` | `0.14.0` | UI 无关的 HTTP/WebSocket/Core 状态机 |
43
- | `packages/web-components` | `@uniplat/ai-employee-web-components` | `0.27.0` | Lit Web Components 与独立浏览器 runtime |
43
+ | `packages/web-components` | `@uniplat/ai-employee-web-components` | `0.29.2` | Lit Web Components 与独立浏览器 runtime |
44
44
  | `examples/vanilla` | `@uniplat/ai-employee-vanilla-example` | 私有示例 | 必须消费 workspace 包的构建产物 |
45
45
 
46
46
  注意:根包改名为 `@csntgao/uni-base` 不代表两个 workspace 包、已有 Web Component 标签或公开类型可以随之改名。`@uniplat/*` 名称和 `uniplat-*` 标签仍是公开契约。
@@ -80,6 +80,11 @@ d19ac72 feat(web-components): 无归属用户强制走创建组织流程
80
80
  acfeed4 feat(web-components)!: 强制入驻去掉跳过开关并恢复邀请码加入
81
81
  efd56ef fix(web-components): 去掉强制入驻邀请码入口并隐藏无身份工作台
82
82
  2aadb87 feat(web-components): 组织邀请页豁免强制入驻
83
+ e4738bb fix(web-components): 入驻流程改为居中对话框
84
+ f20ec7b feat(web-components): 入驻右上角改为按归属显示的取消
85
+ 95bbc8c feat(web-components)!: 团队创建成功页改为单个确认按钮
86
+ ffab3f9 fix(web-components): 非强制入驻的确认按钮点击无反应
87
+ 495ef35 fix(web-components): 创建组织后刷新账户菜单的组织列表
83
88
  ```
84
89
 
85
90
  新运行机制:**个人用户不允许以非员工身份留在系统中**,跳过与“先逛逛”不再存在。当前实现:
@@ -87,8 +92,17 @@ efd56ef fix(web-components): 去掉强制入驻邀请码入口并隐藏无身份
87
92
  1. 账户投影零归属时,`<uniplat-account-context>` 用原生 `dialog.showModal()`(top layer,不受
88
93
  Host 导航 `transform/overflow` 影响)自动打开强制态创建流程:不渲染跳过、无归属提示、邀请码
89
94
  入口与关闭按钮,遮罩点击、`Escape` 与 `cancel` 都不关闭。
90
- 2. 首屏出口只有创建企业、创建团队和“退出登录”(派发 `logout-requested`)。企业/团队表单页的
91
- “跳过”换成“返回”,创建成功页的认证、升级或邀请动作才关闭对话框。
95
+ 2. 首屏出口只有创建企业、创建团队和“退出登录”(派发 `logout-requested`)。企业/团队表单页在
96
+ 强制态下是“返回”回到创建方式选择。
97
+ 2.1 入驻流程一律用原生 `dialog.showModal()` 视口居中,不再用锚定胶囊的下拉浮层;普通态可由
98
+ 遮罩点击、`Escape` 或右上角“取消”关闭,强制态一律不关闭。
99
+ 2.2 右上角“取消”只在当前用户已归属某个组织时渲染(内部 `canCancel`,按投影归属数量传入),
100
+ 点击后直接收起,不再经过“确定暂不创建”确认页——该确认页与其“仍可浏览官网”文案已删除。
101
+ 2.3 创建成功页是终点:企业页保留“稍后认证 / 去认证”,团队页只有一个“确认”并派发
102
+ `onboarding-finished`;`team-upgrade-requested` 与 `team-invite-members-requested` 不再派发。
103
+ 收尾处理器无论强制与否都收起对话框(早退曾导致非强制态“确认”点不动),并再取一次投影,
104
+ 保证新建组织出现在账户菜单的切换列表里。账户菜单在投影变化时也会重新配置,不再因为元素
105
+ 实例未变而停在旧列表。
92
106
  3. **没有可跳过开关**:`organizationOnboarding` 只接收 `transport` 与可选 `invitePaths`,历史
93
107
  `mandatory: false` 不再放行(有用例守着)。
94
108
  4. 没有任何企业员工身份时,账户胶囊仍渲染,但**不显示工作台入口**,即使配置了 `workbench`;
@@ -301,13 +315,13 @@ Account Context 对外只派发净化后的 `LOAD_FAILED`,精确排错应看
301
315
 
302
316
  ## 7. 当前构建和验证状态
303
317
 
304
- 在 `2aadb87` 上最近一次完整检查结果:
318
+ 在 `495ef35` 上最近一次完整检查结果:
305
319
 
306
320
  ```text
307
321
  pnpm format:check 通过
308
322
  pnpm lint 通过
309
323
  pnpm typecheck 通过
310
- pnpm test 25 个测试文件通过;287 passed,2 skipped
324
+ pnpm test 25 个测试文件通过;292 passed,2 skipped
311
325
  pnpm build core、web-components、runtime、vanilla 全部通过
312
326
  ```
313
327
 
@@ -315,7 +329,7 @@ pnpm build core、web-components、runtime、vanilla 全部通过
315
329
 
316
330
  ```text
317
331
  /Users/gaostudio/uni-base/uniplat-ai-employee-client-0.14.0.tgz
318
- /Users/gaostudio/uni-base/uniplat-ai-employee-web-components-0.27.0.tgz
332
+ /Users/gaostudio/uni-base/uniplat-ai-employee-web-components-0.29.2.tgz
319
333
  ```
320
334
 
321
335
  根包本地产物:
@@ -348,9 +362,9 @@ pnpm --filter @uniplat/ai-employee-web-components pack
348
362
 
349
363
  ```text
350
364
  Manifest: https://10.10.10.131:8787/current.json
351
- Version: 0.27.0
352
- Module: https://10.10.10.131:8787/releases/0.27.0/index.js
353
- SHA-256: a5dfb56d11b60b0e3d475b24b2590751f830806e0907ed7013a38fdf05fae1f5
365
+ Version: 0.29.2
366
+ Module: https://10.10.10.131:8787/releases/0.29.2/index.js
367
+ SHA-256: 60fbeac569ff3cb5e27f7e4ef650fc5114343c44b42a3be3f86ace1da1bb1de9
354
368
  ```
355
369
 
356
370
  最后验证结果:
@@ -358,7 +372,7 @@ SHA-256: a5dfb56d11b60b0e3d475b24b2590751f830806e0907ed7013a38fdf05fae1f5
358
372
  - `current.json`:HTTP 200、`Cache-Control: no-store`;
359
373
  - 版本模块:HTTP 200、CORS `*`、`immutable`;
360
374
  - 远端模块 hash 与本地一致;
361
- - `current.json` 指向 `./releases/0.27.0/index.js`。
375
+ - `current.json` 指向 `./releases/0.29.2/index.js`。
362
376
 
363
377
  静态服务未启动时可执行:
364
378
 
@@ -380,17 +394,20 @@ https://registry.npmjs.org/
380
394
 
381
395
  ```text
382
396
  npm whoami csntgao(已登录)
383
- @csntgao/uni-base 0.4.0 已发布并回读校验
397
+ @csntgao/uni-base 0.6.2 已发布并回读校验
384
398
  @uniplat/ai-employee-client 未发布,PUT 返回 404(无 scope 创建权限)
385
399
  @uniplat/ai-employee-web-components 未发布,同上
386
400
  ```
387
401
 
388
- `@csntgao/uni-base@0.4.0` 的远端 shasum 为 `51397b9e1f897641ea265ba4c74621c81d4a6fc4`,
402
+ `@csntgao/uni-base@0.6.2` 的远端 shasum 为 `9335c2d6e2e32a7bec7c282ef7cce65c90b8d70a`,
389
403
  与本地打包一致。发布后远端 `npm view` 有几秒传播延迟,第一次回读到旧版本要重试再确认。注意该包没有 `main`/`exports`/`files`,发布内容是 91 个源码文件,
390
404
  不是可直接 `import` 的库;调用方仍应使用 runtime 或 workspace 包。
391
405
 
392
- 发布需要浏览器 2FA。本机 shell 不是交互终端,直接 `npm publish` 会以 `EOTP` 退出;
393
- 用伪终端包一层即可让 npm 停下来等浏览器授权:
406
+ 发布需要浏览器 2FA:该账号的第二因子是 passkey / WebAuthn,**没有 TOTP 六位码**,所以
407
+ `npm publish --otp=<code>` 这条路走不通。账号 2FA 当前仍是 `auth-and-writes`,改为 `auth-only`
408
+ 的命令是 `npm profile enable-2fa auth-only`(需要账号密码 + 一次浏览器验证),本次未完成。
409
+ 本机 shell 不是交互终端,直接 `npm publish` 会以 `EOTP` 退出;用伪终端包一层即可让 npm
410
+ 停下来等浏览器授权:
394
411
 
395
412
  ```bash
396
413
  script -q /dev/null npm publish <绝对路径或 tarball> --access public