@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.
- package/AGENTS.md +6 -4
- package/CLAUDE.md +6 -4
- package/README.md +6 -2
- 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 +46 -0
- package/package.json +12 -13
- package/packages/core/package.json +1 -1
- package/packages/core/src/assistant-actions.ts +120 -0
- package/packages/core/src/client.ts +21 -7
- package/packages/core/src/customer-support-types.ts +15 -0
- package/packages/core/src/index.ts +8 -0
- package/packages/core/src/protocol.ts +5 -7
- package/packages/core/src/types.ts +6 -0
- package/packages/core/tests/assistant-actions.test.ts +76 -0
- package/packages/core/tests/client.test.ts +96 -0
- package/packages/web-components/package.json +1 -1
- package/packages/web-components/scripts/verify-runtime-build.mjs +1 -0
- package/packages/web-components/src/chat.ts +266 -14
- package/packages/web-components/src/customer-support-transport.ts +117 -15
- package/packages/web-components/src/index.ts +14 -2
- package/packages/web-components/tests/chat.test.ts +417 -9
- package/packages/web-components/tests/customer-support-transport.test.ts +89 -1
- package/restart +0 -0
- package//344/272/244/346/216/245/346/226/207/346/241/243.md +34 -17
|
@@ -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
|
-
> 交接基线提交:`
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
-
在 `
|
|
318
|
+
在 `495ef35` 上最近一次完整检查结果:
|
|
305
319
|
|
|
306
320
|
```text
|
|
307
321
|
pnpm format:check 通过
|
|
308
322
|
pnpm lint 通过
|
|
309
323
|
pnpm typecheck 通过
|
|
310
|
-
pnpm test 25 个测试文件通过;
|
|
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.
|
|
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.
|
|
352
|
-
Module: https://10.10.10.131:8787/releases/0.
|
|
353
|
-
SHA-256:
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
393
|
-
|
|
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
|