@numa-tech/numa 1.24.0 → 1.26.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.
Files changed (97) hide show
  1. package/README.md +4 -0
  2. package/dist/business-issuer.js +5 -5
  3. package/dist/business-issuer.js.map +1 -1
  4. package/dist/cli.js +3 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.js +2 -2
  8. package/dist/config.js.map +1 -1
  9. package/dist/kb/catalog.js +33 -2
  10. package/dist/kb/catalog.js.map +1 -1
  11. package/dist/kb/chat-stream.d.ts +5 -0
  12. package/dist/kb/chat-stream.js +61 -0
  13. package/dist/kb/chat-stream.js.map +1 -0
  14. package/dist/kb/client.d.ts +75 -6
  15. package/dist/kb/client.js +114 -12
  16. package/dist/kb/client.js.map +1 -1
  17. package/dist/kb/commands.js +93 -8
  18. package/dist/kb/commands.js.map +1 -1
  19. package/dist/kb/config.d.ts +15 -3
  20. package/dist/kb/config.js +40 -21
  21. package/dist/kb/config.js.map +1 -1
  22. package/dist/kb/management-commands.d.ts +7 -0
  23. package/dist/kb/management-commands.js +86 -0
  24. package/dist/kb/management-commands.js.map +1 -0
  25. package/dist/kb/management-schemas.d.ts +106 -0
  26. package/dist/kb/management-schemas.js +33 -0
  27. package/dist/kb/management-schemas.js.map +1 -0
  28. package/dist/kb/transfers.d.ts +4 -0
  29. package/dist/kb/transfers.js +11 -4
  30. package/dist/kb/transfers.js.map +1 -1
  31. package/dist/kb/upload-decisions.d.ts +28 -0
  32. package/dist/kb/upload-decisions.js +23 -0
  33. package/dist/kb/upload-decisions.js.map +1 -0
  34. package/dist/kb/upload-state.d.ts +1 -0
  35. package/dist/kb/upload-state.js +1 -0
  36. package/dist/kb/upload-state.js.map +1 -1
  37. package/dist/kb/web-login.d.ts +10 -0
  38. package/dist/kb/web-login.js +25 -0
  39. package/dist/kb/web-login.js.map +1 -0
  40. package/dist/kb/web-page.d.ts +3 -0
  41. package/dist/kb/web-page.js +114 -0
  42. package/dist/kb/web-page.js.map +1 -0
  43. package/dist/kb/web-queue.d.ts +61 -0
  44. package/dist/kb/web-queue.js +101 -0
  45. package/dist/kb/web-queue.js.map +1 -0
  46. package/dist/kb/web.d.ts +26 -0
  47. package/dist/kb/web.js +234 -0
  48. package/dist/kb/web.js.map +1 -0
  49. package/dist/local-web/context-menu.d.ts +2 -0
  50. package/dist/local-web/context-menu.js +53 -0
  51. package/dist/local-web/context-menu.js.map +1 -0
  52. package/dist/local-web/file-workspace-style.d.ts +2 -0
  53. package/dist/local-web/file-workspace-style.js +58 -0
  54. package/dist/local-web/file-workspace-style.js.map +1 -0
  55. package/dist/local-web/file-workspace.d.ts +2 -0
  56. package/dist/local-web/file-workspace.js +39 -0
  57. package/dist/local-web/file-workspace.js.map +1 -0
  58. package/dist/local-web/security.d.ts +6 -0
  59. package/dist/local-web/security.js +51 -0
  60. package/dist/local-web/security.js.map +1 -0
  61. package/dist/login-recovery.js +8 -3
  62. package/dist/login-recovery.js.map +1 -1
  63. package/dist/mcp-gateway/profile.d.ts +4 -0
  64. package/dist/mcp-gateway/profile.js +6 -3
  65. package/dist/mcp-gateway/profile.js.map +1 -1
  66. package/dist/mcp-gateway/runtime.js +14 -0
  67. package/dist/mcp-gateway/runtime.js.map +1 -1
  68. package/dist/oss/transfer-queue.d.ts +7 -51
  69. package/dist/oss/transfer-queue.js +15 -308
  70. package/dist/oss/transfer-queue.js.map +1 -1
  71. package/dist/oss/web-page.js +21 -103
  72. package/dist/oss/web-page.js.map +1 -1
  73. package/dist/oss/web.js +4 -45
  74. package/dist/oss/web.js.map +1 -1
  75. package/dist/platform-profile.d.ts +25 -1
  76. package/dist/platform-profile.js +44 -1
  77. package/dist/platform-profile.js.map +1 -1
  78. package/dist/transfers/persistent-queue.d.ts +65 -0
  79. package/dist/transfers/persistent-queue.js +312 -0
  80. package/dist/transfers/persistent-queue.js.map +1 -0
  81. package/docs/design/kb-capability-upgrade.md +36 -0
  82. package/docs/design/kb-cli.md +4 -4
  83. package/docs/design/kb-web-workspace.md +97 -0
  84. package/docs/kb-access.md +86 -7
  85. package/examples/jev/README.md +65 -0
  86. package/examples/jev/platform-client.test.ts +136 -0
  87. package/examples/jev/platform-client.ts +122 -0
  88. package/package.json +9 -7
  89. package/scripts/export-kb-contract.py +25 -6
  90. package/skills/numa-jev/LICENSE +21 -0
  91. package/skills/numa-jev/SKILL.md +54 -0
  92. package/skills/numa-jev/assets/decision.json +37 -0
  93. package/skills/numa-jev/numa-skill.json +8 -0
  94. package/skills/numa-jev/references/application-integration.md +36 -0
  95. package/skills/numa-jev/references/mcp-usage.md +53 -0
  96. package/skills/numa-jev/references/upstream.md +13 -0
  97. package/src/kb/openapi-contract.json +2297 -566
@@ -0,0 +1,97 @@
1
+ # KB 文档工作台:复用 OSS Web
2
+
3
+ ## 业务映射
4
+
5
+ | 原 OSS 交互 | KB 交互 | 权威接口 |
6
+ | --- | --- | --- |
7
+ | Grant 选择 | 租户选择 | GET /api/v1/auth/tenants |
8
+ | Bucket/存储范围 | 知识库选择 | GET /api/v1/external/agent/collections |
9
+ | 对象列表 | 文档列表、状态、版本 | GET /api/v1/kb/documents |
10
+ | 原件下载 | KB 授权下载 | GET /api/v1/external/agent/documents/{id}/download-ticket |
11
+ | 对象删除 | 文档删除 | DELETE /api/v1/kb/documents/{id} |
12
+
13
+ 仅复用交互,不建立 Grant=租户 或 Bucket=知识库 的物理一对一关系。沿用现有 bucket/prefix,所有访问与删除继续由 KB 检查权限并维护文档、文件与索引。
14
+
15
+ ## 实现
16
+
17
+ 新增 `numa kb web [--port PORT] [--no-open]`,遵守原有 Profile 服务路由和根级 `--issuer`。顶部选择租户、左侧选择知识库、中间分页显示文档、右侧查看详情。传输与入库任务位于独立任务面板。沿用 OSS Web 样式、原生选择器、回环地址保护和后台传输队列,不复制 OSS 的队列状态机。
18
+
19
+ - `src/transfers/persistent-queue.ts`:从 OSS 提取持久化、并发调度、暂停/恢复/取消、进程锁与原子保存;OSS 原有磁盘格式及分片 worker 保持兼容。
20
+ - `src/oss/transfer-queue.ts`:继续负责 Grant/STS、对象路径校验、分片检查点和作用域校验。
21
+ - `src/kb/web-queue.ts`:KB 队列适配器,调用现有 `uploadToKb`/`downloadFromKb`,与 CLI 使用相同 KB 传输逻辑。
22
+ - `src/local-web/context-menu.ts`:OSS 与 KB 共用菜单渲染、屏幕边界定位、键盘导航、外部点击关闭和焦点恢复;业务适配器分别提供可执行操作。KB 接入详情、原件下载和确认删除,未接入 OSS 对象移动、复制、重命名。文件列表的行组件和任务卡片由公共模块渲染;筛选、分页、业务详情和权限判断保留在各自适配器。
23
+ - `src/local-web/file-workspace.ts` / `file-workspace-style.ts`:OSS 与 KB 共用文件行、传输任务卡片及工作台样式;刷新更新现有任务卡片,保留焦点和按钮状态。
24
+ - `src/local-web/security.ts`:OSS 与 KB 共用 Host、Origin、本机会话认证、CSP、JSON 大小和字段校验。
25
+
26
+ 本机工作台不接受浏览器指定任意本地路径;文件与目录必须通过已有原生选择器选取。界面只接收业务数据和任务摘要,Keycloak Token、OSS 凭据与签名地址均留在本地 CLI 进程。
27
+
28
+ ## 租户与任务绑定
29
+
30
+ Profile 的平台 tenant 继续用于身份配置;KB 的业务 tenant 通过 `X-KB-Tenant` 独立传递。切换知识库不改变已有任务的 tenant/collection。队列目录按服务绑定和当前用户身份隔离;恢复任务重新校验所记录租户的成员资格。身份变化时拒绝继续工作,需要重启本地服务。
31
+
32
+ ## 上传和入库
33
+
34
+ 文件/目录扫描后写入任务队列 → 通过 KB 创建上传会话 → 并行上传 OSS → KB complete 校验并提交解析任务。
35
+
36
+ 界面将“传输完成”和“入库完成”分开展示。关闭浏览器不停止本地上传;停止 CLI 会保存状态并暂停传输;已提交的服务端解析不被取消。停止传输仅清理仍在上传的 KB 会话,不删除文档、不撤销已提交的入库任务。
37
+
38
+ **当前 KB 恢复粒度仍是文件级**:已上传文件跳过,未完成文件重新上传。OSS Web 的分片 worker 依赖 Grant/STS,而 KB 当前只签发单次 PUT;本实现不伪造 STS,不把文件恢复标作分片续传。完整分片复用需要先增加 KB 会话的初始化、分片授权、完成与取消契约,再接到同一队列,无需另做 UI。
39
+
40
+ 同名文件/版本冲突继续由 KB 拒绝或提示处理;本地工作台不自动覆盖、不绕过现有版本确认。
41
+
42
+ ## 验收边界
43
+
44
+ 目录选择拆为逐文件任务,统一限制最多 3 个文件传输;沿用 KB 文档的平面命名和版本规则。
45
+
46
+ 测试覆盖租户隔离、身份改变、路径注入、Host/Origin/会话认证、确认删除、持久化暂停与重启、重复任务和后台入库状态,并运行已有 KB/OSS 回归。
47
+
48
+ 本次实现与验证保持本地,不执行真实文档上传/删除,也不修改线上 bucket、Grant、租户或知识库。公开 Numa 1.25.0 尚不包含此新增命令。
49
+
50
+ ## 环境选择
51
+
52
+ 页面选择顺序为环境 → 租户 → 知识库。环境 ID 是 Profile 下发的服务路由键;`namespace` 仅作展示,不能用于拼接地址或决定身份。`envId` 可记录平台环境 ID。环境决定完整的 baseUrl、resource、authority、audience、roleClientId、apiVersion;authority 引用同一 Profile 的认证配置。
53
+
54
+ 使用已有 `services.kb.configuration` JSON 扩展 `environments` 与 `defaultEnvironment`,复用平台已有服务配置存储和发布流程,无需增加另一套环境配置文件或数据库表。发布者必须同时维护对应 authority;客户端在使用前验证引用、完整字段和地址。默认环境的路由必须与顶层 KB 服务字段一致,旧 CLI 继续使用顶层字段。
55
+
56
+ - 未发布环境列表:显示“当前默认配置”,保持旧配置行为。
57
+ - 只有环境列表、无显式默认项:保留现有默认服务,并提供其他环境选项。
58
+ - `kb --environment <code> ...`:所有 KB 命令选择该环境。
59
+ - `login/auth status/auth logout --service kb --environment <code>`:使用相同环境身份与缓存。
60
+ - `--environment` 与 `--issuer` 互斥;环境选择不能再独立换 issuer。
61
+ - 每个 HTTP 请求携带环境 ID,服务端只接受启动时 Profile 中的环境;不接受浏览器传入 endpoint。
62
+ - 环境分别创建队列,并绑定端点与用户;切换页面不重定向或取消原任务。登录身份变化则阻止该环境继续处理。
63
+ - 多个环境可以共享 issuer/client/audience,复用同一认证缓存;业务端点和任务目录仍分开。
64
+ - 新环境未登录时使用 `numa login --service kb --environment <code>`,不复用另一个环境的不匹配 Token。
65
+ - 删除确认保存原始环境和租户,切换页面后仍指向确认时的对象;弹窗展示环境和租户。
66
+
67
+ 示例(仅说明结构,地址和 authority 必须来自平台实际绑定):
68
+
69
+ ```json
70
+ {
71
+ "defaultEnvironment": "stg",
72
+ "environments": {
73
+ "stg": {
74
+ "name": "预发布环境",
75
+ "namespace": "kb-stg",
76
+ "envId": 2,
77
+ "baseUrl": "https://gateway-stg.example/kb-management",
78
+ "resource": "https://gateway-stg.example/kb-management",
79
+ "authority": "kb-stg-login",
80
+ "audience": "kb-management",
81
+ "roleClientId": "kb-management",
82
+ "apiVersion": "v1"
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ 本地测试使用两套隔离模拟端点验证路由、Token、租户选择和删除目标;未修改线上 Profile,真实环境选项仍以平台已发布内容为准。
89
+
90
+
91
+ ## 工作台登录恢复
92
+
93
+ 工作台先启动受本地会话认证保护的页面;缺失登录不阻止启动。初次进入或选择环境后,401 / `kb_auth_required` 可由页面发起同源 POST `/api/login?environment=...`。服务端按已发布环境的配置调用共享 SDK 的 `startBrowserLogin` 和 `completeBrowserLogin`,保留 PKCE、state、回调校验和相同缓存键,不另行实现 OAuth。
94
+
95
+ 回调地址只由本地服务生成,返回当前工作台及环境,页面等待服务端完成 Token 交换和身份读取后再载入租户。一个本地服务同一时间只允许一个交互登录,重复请求复用进行中的任务。只读轮询和后台队列不触发登录;不重放写请求。关闭服务会取消等待并关闭回调监听。403 和认证连接失败不触发登录。已存在队列的用户身份不能随重新登录改变,换账号需重启并使用独立队列。
96
+
97
+ 返回页面的本地会话标识仅在 URL fragment 中,OIDC Token 始终留在 CLI 缓存;浏览器业务接口不返回 Token、授权码或凭据。
package/docs/kb-access.md CHANGED
@@ -6,9 +6,7 @@
6
6
 
7
7
  平台 Profile 提供 `services.kb` 时,CLI 使用其服务地址、身份源与 audience。没有 Profile 时需显式配置 `NUMA_KB_BASE_URL`,其值是包含网关服务前缀的根地址,例如 `https://gateway.example.com/kb-management`,不追加 `/api/v1`。有 Profile 时不能用环境变量改到其他服务。
8
8
 
9
- KB API Key 使用 `NUMA_KB_API_KEY_FILE` 指向已有的私有凭据文件,或使用 `NUMA_KB_API_KEY` 环境变量,二者不能同时配置。Key 应由知识库授权主体签发,范围覆盖目标知识库和所需操作。不要将 Key 写入命令参数或检查点。
10
-
11
- 默认同时使用 Keycloak 和 API Key。独立 KB 服务使用对应的登录缓存:
9
+ 默认 `NUMA_KB_AUTH_MODE=keycloak`,仅使用所选服务的 Keycloak Bearer JWT,不要求或发送 KB API Key。Skill 可直接执行 `numa kb`,无需注册 MCP;CLI 复用现有服务 Profile、登录缓存及 Token 刷新能力。独立 KB 服务使用对应的登录缓存:
12
10
 
13
11
  ```bash
14
12
  numa login --service kb --no-console --no-config
@@ -16,9 +14,18 @@ numa auth status --service kb --json
16
14
  numa profile --service kb --json
17
15
  ```
18
16
 
19
- 机器身份沿用 Numa 的 `NUMA_KEYCLOAK_AUTH_MODE=client_credentials` 和专用 client 配置,无需浏览器登录。对只要求 KB API Key 的部署,可显式设置 `NUMA_KB_AUTH_MODE=api-key`。CLI 不会在认证失败时自动降级或更换身份。
17
+ 机器身份沿用 Numa 的 `NUMA_KEYCLOAK_AUTH_MODE=client_credentials` 和专用 client 配置,无需浏览器登录。仅对仍支持旧 KB API Key 的部署,可显式设置 `NUMA_KB_AUTH_MODE=api-key`,再用 `NUMA_KB_API_KEY_FILE` 指向私有凭据文件,或设置 `NUMA_KB_API_KEY`(二者互斥)。该模式仅发送 Key,不发送 Bearer。默认 JWT 模式忽略这两个旧 Key 配置,即使环境中仍残留,也不会读取 Key 文件或发送双头。CLI 不会在认证失败时自动降级或更换身份。不要将凭据写入 Skill、命令参数或检查点。
18
+
19
+ Profile 的 `services.kb.authority` 是默认身份源;如平台同时下发了 `services.kb.issuerAuthorities`,可使用已有的 `--issuer` 选择其中的别名。登录、状态、请求和退出必须选择相同别名:
20
+
21
+ ```bash
22
+ numa login --service kb --issuer BUSINESS_ALIAS --no-console --no-config
23
+ numa auth status --service kb --issuer BUSINESS_ALIAS --json
24
+ numa --issuer BUSINESS_ALIAS kb collections list --json
25
+ numa auth logout --service kb --issuer BUSINESS_ALIAS
26
+ ```
20
27
 
21
- 退出 KB 登录使用 `numa auth logout --service kb`。这不会删除其他服务的缓存。
28
+ 不传 `--issuer` 时使用平台默认配置;未知别名和未下发的身份源会被拒绝,不接受任意 Issuer URL。该选项只选择服务允许的身份源,不改变 KB 服务地址。退出只删除对应 Issuer/Client/租户/Audience 的会话缓存。
22
29
 
23
30
  ## 基本操作
24
31
 
@@ -55,7 +62,7 @@ numa kb tasks status TASK --wait --timeout 600 --json
55
62
  numa kb tasks retry TASK --json
56
63
  ```
57
64
 
58
- 首次上传在传输前保存检查点;不指定 `--state-file` 时自动生成,并在回执中返回路径。检查点只保存目标绑定、文件摘要、固定请求 ID、会话和任务 ID,不保存 API Key、Token 或签名 URL。上传中断后保留该文件,使用相同文件源和凭据配置运行 `--resume`;本地文件变化或目标身份变化时拒绝恢复。
65
+ 首次上传在传输前保存检查点;不指定 `--state-file` 时自动生成,并在回执中返回路径。检查点只保存目标绑定、文件摘要、固定请求 ID、会话和任务 ID,不保存 API Key、Token 或签名 URL。上传中断后保留该文件,使用相同文件源和凭据配置运行 `--resume`;本地文件变化或目标绑定变化时拒绝恢复。绑定包含服务地址、认证模式、配置的租户、Issuer、Client 和 Audience;它不是当前登录用户的标识,不能代替 KB 对会话所有者的校验。旧的 JWT + Key 检查点因认证绑定变化会被拒绝复用;先查询原任务/会话结果,再决定是否发起新上传,勿直接重复提交。
59
66
 
60
67
  恢复是文件级:向 KB 查询哪些对象已上传,跳过已上传项,只重新传输缺失项。单次 PUT 中断后从该文件起点重新开始。CLI 不会为已提交的会话重复创建解析任务。Ctrl-C 中止当前工作并保留恢复记录。
61
68
 
@@ -82,4 +89,76 @@ npm run test:kb
82
89
  npm run root:typecheck
83
90
  ```
84
91
 
85
- 去掉 `--check` 可在审查服务端变化后更新快照。导出不连接部署环境,不加载真实业务数据。受控本地 HTTP 测试覆盖协议和文件行为,不能代替目标环境的真实网关、Keycloak、API Key 和 OSS 联调。
92
+ 去掉 `--check` 可在审查服务端变化后更新快照。导出不连接部署环境,不加载真实业务数据。受控本地 HTTP 测试覆盖协议和文件行为,不能代替目标环境的真实网关、Keycloak 和 OSS 联调(旧 Key 模式另行验证)。
93
+ ## 本地文档工作台(待发布)
94
+
95
+ 新增 `numa kb web`:选择租户与知识库,查询文档、上传文件或目录、下载原件、确认删除,并查看传输及后台入库进度。使用与 `kb upload` 相同的 Profile/JWT 身份。`--no-open` 不自动打开浏览器,`--port` 指定回环端口。
96
+
97
+ 在本分支完成依赖安装与 `npm run root:build` 后,本地运行:
98
+
99
+ ```bash
100
+ node dist/cli.js kb web
101
+ ```
102
+
103
+ 选择目录会递归生成逐文件任务,最多同时传输 3 个文件。目录结构不映射为知识库目录,同名与版本冲突沿用 KB 规则。
104
+
105
+ 关闭页面后上传继续;停止 CLI 后未完成的传输暂停,已提交的入库任务继续执行。重启同一身份的工作台后可继续任务。KB 目前按文件恢复,不支持字节级分片续传。此功能尚未包含在公开 1.25.0 包中;实现边界见 [设计](design/kb-web-workspace.md)。
106
+
107
+
108
+ 平台下发多环境配置后,可在工作台选择环境,也可通过 CLI 指定:
109
+
110
+ ```bash
111
+ numa login --service kb --environment <环境代码>
112
+ numa kb --environment <环境代码> web
113
+ numa kb --environment <环境代码> collections list
114
+ ```
115
+
116
+ 环境代码来自 Profile,不能填写任意网关地址;未下发列表时沿用当前默认配置。切换环境会一起切换 KB 地址、issuer、Client 和 audience,原环境的后台传输继续绑定原目标。
117
+
118
+
119
+ `kb web` 在所选环境未登录或会话失效时,会通过现有 PKCE 登录流程自动打开该环境的 Keycloak,登录成功后返回同一个 KB 工作台和环境。也可点击“登录当前环境”重新发起登录。有效缓存会直接复用;403 权限不足和认证服务网络错误不自动登录。取消或失败后需手动重试,不持续弹窗。
120
+
121
+ `--no-open` 不自动打开工作台或发起登录;手动打开工作台后仍可点击登录按钮。机器身份不提供浏览器登录。后台传输不会自动弹出登录窗口,认证失效时保留失败任务,登录后手动恢复;不会自动重放删除等写操作。
122
+
123
+ ## 1.26 核心能力升级
124
+
125
+ 跨库检索直接使用服务端统一召回/排序,一次选择 1–20 个库:
126
+
127
+ ```bash
128
+ npx -y @numa-tech/numa@1.26.0 kb --environment ENV --tenant TENANT search "部署规范" --collection-ids KB_A KB_B --json
129
+ npx -y @numa-tech/numa@1.26.0 kb search "部署规范" --all-collections --json
130
+ ```
131
+
132
+ 单库、多库、全部可见库三个范围互斥。全部指当前租户内可见的库,超过 20 个明确拒绝;
133
+ 不跨环境合并身份,也不在客户端拼接评分。`_meta.search_complete=false` 表示部分库失败,
134
+ `failed_collection_ids` 标出缺失范围;sources 保留文档所属 collection_id。
135
+
136
+ 新增 `tenants`、`users`、`collections inspect/update/delete/transfer/members`、
137
+ `documents list/inspect/chunks/assets/versions/history/switch-version/update/delete/reindex`、
138
+ `tasks list/cancel/jobs`、`conflicts list/resolve`、`ask`、`sessions list/create/inspect/delete`。
139
+ 精确参数见各命令 `--help`。文档正文需 `inspect --content`,列表用 offset/limit 翻页。
140
+ 写操作继续使用当前 JWT 和服务端 ACL;删除、转移、重建、版本切换和冲突决策要求 `--yes`。
141
+ 版本切换必须携带读取到的 `--expected-current`,409 后重新读取,不强制覆盖。
142
+
143
+ ```bash
144
+ numa kb preflight ./docs --collection-id KB --json
145
+ numa kb ask "这些规范有何不同?" --collection-ids KB_A KB_B --json
146
+ numa kb sessions create --collection-ids KB_A KB_B --title "规范对比" --json
147
+ numa kb documents versions DOC --json
148
+ numa kb documents switch-version DOC --target TARGET --expected-current CURRENT --reason "批准修订" --yes --json
149
+ ```
150
+
151
+ `ask` 返回回答与检索来源,指定 `--session-id` 才保存本次问题/回答;流中断后先读取会话,
152
+ 不自动重试写入。`preflight` 只发送摘要;根据候选决定上传新版本时,传入
153
+ `--decisions-file ./decisions.json --yes`,文件内容为数组:
154
+
155
+ ```json
156
+ [{"filename":"report.pdf","sha256":"预检返回的64位SHA256","duplicate_action":"new_version","existing_document_id":"目标文档ID","version_reason":"批准修订","version_confirmation":"SWITCH_CURRENT_VERSION"}]
157
+ ```
158
+
159
+ 恢复必须使用同一决策文件。决策与摘要不匹配拒绝;旧代理上传不能应用版本决策时明确拒绝。
160
+ 当前后端已退役 multipart 上传,旧兼容契约单独保存在 `openapi-legacy-contract.json`;
161
+ 当前 `openapi-contract.json` 由现有后端 42 个操作离线生成,不能以旧契约断言当前支持代理上传。
162
+
163
+ 统一 `numa-kb 1.1.0` Skill 覆盖全部流程,要求 CLI >=1.26.0。包版本号仅表示实现目标;
164
+ 实际可用性以 npm latest 与技能仓库发布状态为准。
@@ -0,0 +1,65 @@
1
+ # Jev 三场景接入样例
2
+
3
+ 本目录和 [numa-jev Skill](../../skills/numa-jev/SKILL.md) 是源码交付;未发布到企业仓库,未部署到目标环境。OpenRouter 仅由 DevOps 平台经指定代理调用。测试完全离线,不使用真实 Key 或推理额度。
4
+
5
+ ## 终端 Agent / WorkBuddy
6
+
7
+ 分别安装经过核验的 Skill 与 MCP 资源。安装步骤沿用 [Numa Skills/MCP 工作流](../../skills/numa-cli/references/skills-workflows.md),不要将资源名、namespace、instance、版本猜成文档中的示例值。
8
+
9
+ 工具发现后调用 `numa_jev_capabilities({})`。将 [合成请求](../../skills/numa-jev/assets/decision.json) 放入 `numa_jev_decide` 参数的 `body` 中。成功取 `structuredContent.data.answers`;HTTP API 不使用这个封装。连接、Jev 权限、上游能力分别核验。
10
+
11
+ ## ChatGPT
12
+
13
+ 目标为正式远程 HTTPS MCP + 用户 OAuth,使用同样的工具合同。现有源码的本地 stdio 服务或安装 Skill 不能证明该链路可用。上线必须补齐远程 OAuth 兼容、实际 Router 导出和客户端连接验证;工具说明承担本地 Skill 中必要的用法指导。
14
+
15
+ 验收时用真实授权测试用户检查:连接 OAuth 完成、工具可见、capabilities 可读、一次合成请求成功;无 Jev 权限的身份不能执行推理。这里只列验收步骤,不执行外部调用或创建连接。
16
+
17
+ ## K8s 自研 Agent
18
+
19
+ [platform-client.ts](platform-client.ts) 展示固定平台地址、服务令牌注入、无自动重放的 HTTP 调用。它是供复制调整的薄示例,不是已发布 SDK;业务应按自己的 schema 检查答案类型/概率并应用经过验证的阈值。网关前缀保留在 base URL 中,例如 `https://platform.example.invalid/devops`。
20
+
21
+ Node 应用可复用已核实版本的 `@numa-tech/cli-auth`,而非自己保存/解析用户 token 缓存:
22
+
23
+ ```ts
24
+ import { ensureAccessToken, type AuthConfig } from "@numa-tech/cli-auth";
25
+ import { createJevClient } from "./platform-client.js";
26
+
27
+ // All values come from the workload's reviewed environment configuration.
28
+ // JEV_OIDC_CLIENT_SECRET is injected by a K8s Secret, never committed.
29
+ const auth: AuthConfig = {
30
+ authenticationMode: "client_credentials",
31
+ discoveryUrl: process.env.JEV_OIDC_DISCOVERY_URL!,
32
+ issuerUrl: process.env.JEV_OIDC_ISSUER!,
33
+ clientId: process.env.JEV_OIDC_CLIENT_ID!,
34
+ clientSecret: process.env.JEV_OIDC_CLIENT_SECRET!,
35
+ scope: process.env.JEV_OIDC_SCOPE ?? "",
36
+ // Required by the shared config type; machine mode never writes user caches.
37
+ tokenFile: "/unused-in-client-credentials-mode",
38
+ redirectHost: "127.0.0.1",
39
+ redirectPort: 8765,
40
+ redirectPath: "/callback",
41
+ openBrowser: false
42
+ };
43
+ const client = createJevClient({
44
+ platformBaseUrl: process.env.JEV_PLATFORM_BASE_URL!,
45
+ getAccessToken: () => ensureAccessToken(auth)
46
+ });
47
+ // Call client.decide(request) inside the application's authorized workflow.
48
+ // Validate required environment variables at service startup. Do not log auth.
49
+ ```
50
+
51
+ 部署配置应包含平台/issuer 地址、独立服务 client、Secret 注入、相应 Jev 功能授权及到这些企业服务的网络权限。`OPENROUTER_API_KEY`、代理配置及代理认证仅归平台。若应用自身使用出网代理访问企业服务,按现有客户端网络配置处理;不能以此替代平台的 OpenRouter 代理绑定。
52
+
53
+ ## 离线验证与待验收项
54
+
55
+ 从 Numa 仓库根目录运行:
56
+
57
+ ```sh
58
+ node --import tsx --test examples/jev/platform-client.test.ts
59
+ ```
60
+
61
+ 测试验证路径前缀、HTTP/MCP 请求分层、每次操作令牌获取、鉴权失败不调用平台、401/403/429/5xx/断连无重放、缺少答案失败及禁止凭据 URL。合成答案只用于 transport 测试,不表示模型质量。
62
+
63
+ 示例仅保留白名单平台错误码和 HTTP 状态,有界读取错误响应;身份 SDK 的原始异常转换为 `IDENTITY_UNAVAILABLE`,且不会派发推理请求。遇到新增未知错误码仍保留状态,按平台维护后的白名单更新示例,不输出上游原文。
64
+
65
+ 目标环境上线后另外验证:用户与服务身份授权;OpenRouter 必须有代理;代理停用后无直连回退;ChatGPT OAuth 的真实往返;用量与审计归属正确。真实端到端验收会调用收费上游,须使用新配置的有效密钥与明确授权的环境。本目录离线验证不宣称这些上线项完成。
@@ -0,0 +1,136 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import test from "node:test";
4
+ import { createJevClient, JevCallError, type JevRequest } from "./platform-client.js";
5
+
6
+ const body: JevRequest = JSON.parse(await readFile(new URL("../../skills/numa-jev/assets/decision.json", import.meta.url), "utf8"));
7
+ const responseBody = {
8
+ model: "synthetic-offline-fixture",
9
+ answers: {
10
+ urgent: { type: "noul", noul: 0.9 },
11
+ department: { type: "choice", choice: "billing", probabilities: { billing: 0.85, technical: 0.05, sales: 0.05, other: 0.05 }, confidence: 0.6 },
12
+ frustration: { type: "score", score: 1.1, legend: { "0": "calm", "1": "frustrated", "2": "angry" }, probabilities: { "0": 0.1, "1": 0.7, "2": 0.2 }, confidence: 0.3 }
13
+ },
14
+ usage: { input_tokens: 0, output_tokens: 0 },
15
+ id: "offline-fixture"
16
+ };
17
+
18
+ test("K8s HTTP transport preserves the gateway prefix and sends only the platform contract", async () => {
19
+ let calls = 0;
20
+ const client = createJevClient({
21
+ platformBaseUrl: "https://platform.example.invalid/devops/",
22
+ getAccessToken: async () => "fake-platform-token",
23
+ fetch: async (input, init) => {
24
+ calls++;
25
+ assert.equal(String(input), "https://platform.example.invalid/devops/api/v1/jev/decisions");
26
+ assert.equal(init?.redirect, "error");
27
+ assert.equal(new Headers(init?.headers).get("authorization"), "Bearer fake-platform-token");
28
+ assert.deepEqual(JSON.parse(String(init?.body)), body);
29
+ assert.equal(init?.method, "POST");
30
+ return Response.json(responseBody);
31
+ }
32
+ });
33
+ assert.deepEqual(await client.decide(body), responseBody);
34
+ assert.equal(calls, 1);
35
+ });
36
+
37
+ for (const status of [401, 403, 429, 502, 503]) {
38
+ test(`HTTP ${status} never triggers a second possibly billable POST`, async () => {
39
+ let calls = 0;
40
+ const client = createJevClient({
41
+ platformBaseUrl: "https://platform.example.invalid",
42
+ getAccessToken: async () => "fake-platform-token",
43
+ fetch: async () => {
44
+ calls++;
45
+ return Response.json({ message: "private-upstream-detail" }, { status });
46
+ }
47
+ });
48
+ await assert.rejects(client.decide(body), (error: unknown) => {
49
+ assert.ok(error instanceof JevCallError);
50
+ assert.equal(error.status, status);
51
+ assert.equal(error.message.includes("private-upstream-detail"), false);
52
+ return true;
53
+ });
54
+ assert.equal(calls, 1);
55
+ });
56
+ }
57
+
58
+ test("network interruption is unknown result, not a new request or direct fallback", async () => {
59
+ let calls = 0;
60
+ const client = createJevClient({
61
+ platformBaseUrl: "https://platform.example.invalid",
62
+ getAccessToken: async () => "fake-platform-token",
63
+ fetch: async () => { calls++; throw new Error("transport detail containing credentials"); }
64
+ });
65
+ await assert.rejects(client.decide(body), { code: "RESULT_UNKNOWN" });
66
+ assert.equal(calls, 1);
67
+ });
68
+
69
+ test("missing answers cannot be consumed as a successful empty decision", async () => {
70
+ const client = createJevClient({
71
+ platformBaseUrl: "https://platform.example.invalid",
72
+ getAccessToken: async () => "fake-platform-token",
73
+ fetch: async () => Response.json({ model: "fixture", answers: {} })
74
+ });
75
+ await assert.rejects(client.decide(body), { code: "MISSING_ANSWER" });
76
+ });
77
+
78
+ test("identity failure prevents dispatch and token is acquired per logical operation", async () => {
79
+ let tokenRequests = 0;
80
+ let calls = 0;
81
+ const client = createJevClient({
82
+ platformBaseUrl: "https://platform.example.invalid",
83
+ getAccessToken: async () => {
84
+ if (++tokenRequests === 2) throw new Error("issuer raw error containing fixture-secret");
85
+ return "fake-platform-token";
86
+ },
87
+ fetch: async () => { calls++; return Response.json(responseBody); }
88
+ });
89
+ await client.decide(body);
90
+ await assert.rejects(client.decide(body), (error: unknown) => {
91
+ assert.ok(error instanceof JevCallError);
92
+ assert.equal(error.code, "IDENTITY_UNAVAILABLE");
93
+ assert.equal(error.message.includes("fixture-secret"), false);
94
+ assert.equal(error.cause, undefined);
95
+ return true;
96
+ });
97
+ assert.equal(tokenRequests, 2);
98
+ assert.equal(calls, 1);
99
+ });
100
+
101
+ test("known platform codes survive while provider details and unknown codes stay private", async () => {
102
+ for (const code of ["OUTBOUND_PROXY_REQUIRED", "JEV_AUDIT_UNAVAILABLE", "fixture-secret"]) {
103
+ const client = createJevClient({
104
+ platformBaseUrl: "https://platform.example.invalid",
105
+ getAccessToken: async () => "fake-platform-token",
106
+ fetch: async () => Response.json({ code, message: "fixture-secret" }, { status: 503 })
107
+ });
108
+ await assert.rejects(client.decide(body), (error: unknown) => {
109
+ assert.ok(error instanceof JevCallError);
110
+ assert.equal(error.code, code === "fixture-secret" ? "PLATFORM_HTTP_ERROR" : code);
111
+ assert.equal(error.status, 503);
112
+ assert.equal(error.message.includes("fixture-secret"), false);
113
+ return true;
114
+ });
115
+ }
116
+ });
117
+
118
+ test("oversized error streams are cancelled before interpreting untrusted error content", async () => {
119
+ let cancelled = false;
120
+ const client = createJevClient({
121
+ platformBaseUrl: "https://platform.example.invalid",
122
+ getAccessToken: async () => "fake-platform-token",
123
+ fetch: async () => new Response(new ReadableStream<Uint8Array>({
124
+ start(controller) { controller.enqueue(new Uint8Array(16 * 1024 + 1)); },
125
+ cancel() { cancelled = true; }
126
+ }), { status: 502 })
127
+ });
128
+ await assert.rejects(client.decide(body), { status: 502, code: "PLATFORM_HTTP_ERROR" });
129
+ assert.equal(cancelled, true);
130
+ });
131
+
132
+ test("caller credentials cannot be placed in a URL and HTTPS is required", () => {
133
+ for (const platformBaseUrl of ["http://platform.example.invalid", "https://user:secret@platform.example.invalid", "https://platform.example.invalid?token=secret"]) {
134
+ assert.throws(() => createJevClient({ platformBaseUrl, getAccessToken: async () => "unused" }), /trusted HTTPS/u);
135
+ }
136
+ });
@@ -0,0 +1,122 @@
1
+ /** Example transport for K8s services. No upstream key, proxy, or retry logic. */
2
+ export interface JevRequest {
3
+ state: string | Record<string, unknown> | unknown[];
4
+ questions: Record<string, unknown>;
5
+ model?: string;
6
+ }
7
+
8
+ export interface JevResult {
9
+ model: string;
10
+ answers: Record<string, unknown>;
11
+ usage?: Record<string, unknown>;
12
+ id?: string;
13
+ provider?: string;
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ export class JevCallError extends Error {
18
+ constructor(public readonly status: number | undefined, public readonly code: string) {
19
+ // Do not include request data, bearer tokens, or the upstream error body.
20
+ super(`Jev call failed (${status ?? "transport"}, ${code}); not retried.`);
21
+ this.name = "JevCallError";
22
+ }
23
+ }
24
+
25
+ const SAFE_PLATFORM_CODES = new Set([
26
+ "JEV_ACCESS_DENIED", "JEV_AUTHENTICATION_REQUIRED", "JEV_PERMISSION_UNAVAILABLE",
27
+ "JEV_INVALID_REQUEST", "JEV_REQUEST_TOO_LARGE", "JEV_INVALID_RESPONSE", "JEV_NOT_CONFIGURED",
28
+ "JEV_BUSY", "JEV_AUDIT_UNAVAILABLE", "JEV_BALANCE_INSUFFICIENT", "JEV_RATE_LIMITED",
29
+ "JEV_PROXY_AUTH_REQUIRED", "JEV_UPSTREAM_FORBIDDEN", "JEV_UPSTREAM_REJECTED",
30
+ "JEV_UPSTREAM_TIMEOUT", "JEV_UPSTREAM_UNAUTHORIZED", "JEV_UPSTREAM_UNAVAILABLE",
31
+ "OUTBOUND_PROXY_REQUIRED", "OUTBOUND_PROXY_UNAVAILABLE", "OUTBOUND_PROXY_AUTH_REQUIRED",
32
+ "OUTBOUND_CONNECT_FAILED", "OUTBOUND_CREDENTIAL_UNAVAILABLE", "OUTBOUND_INVALID_CONFIGURATION"
33
+ ]);
34
+
35
+ async function safeErrorCode(response: Response): Promise<string> {
36
+ const fallback = "PLATFORM_HTTP_ERROR";
37
+ const reader = response.body?.getReader();
38
+ if (!reader) return fallback;
39
+ try {
40
+ let bytes = 0;
41
+ let text = "";
42
+ const decoder = new TextDecoder();
43
+ while (true) {
44
+ const chunk = await reader.read();
45
+ if (chunk.done) break;
46
+ bytes += chunk.value.byteLength;
47
+ if (bytes > 16 * 1024) return fallback;
48
+ text += decoder.decode(chunk.value, { stream: true });
49
+ }
50
+ const value: unknown = JSON.parse(text + decoder.decode());
51
+ if (value && typeof value === "object" && "code" in value
52
+ && typeof value.code === "string" && SAFE_PLATFORM_CODES.has(value.code)) return value.code;
53
+ return fallback;
54
+ } catch {
55
+ return fallback;
56
+ } finally {
57
+ await reader.cancel().catch(() => undefined);
58
+ reader.releaseLock();
59
+ }
60
+ }
61
+
62
+ export function createJevClient(options: {
63
+ platformBaseUrl: string;
64
+ getAccessToken: () => Promise<string>;
65
+ fetch?: typeof fetch;
66
+ timeoutMs?: number;
67
+ }) {
68
+ const base = new URL(options.platformBaseUrl);
69
+ if (base.protocol !== "https:" || base.username || base.password || base.search || base.hash) {
70
+ throw new Error("Configure a trusted HTTPS platform base URL without credentials, query, or fragment.");
71
+ }
72
+ base.pathname = `${base.pathname.replace(/\/+$/u, "")}/api/v1/jev/decisions`;
73
+ const timeoutMs = options.timeoutMs ?? 45_000;
74
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("timeoutMs must be positive.");
75
+
76
+ return {
77
+ async decide(body: JevRequest): Promise<JevResult> {
78
+ // Fetch a current service token for each operation; the identity provider
79
+ // wrapper owns expiry-aware in-memory caching, not this transport.
80
+ let token: string;
81
+ try {
82
+ token = await options.getAccessToken();
83
+ if (!token || /\s/u.test(token)) throw new Error();
84
+ } catch {
85
+ // Identity SDKs may include raw issuer responses in exceptions. No
86
+ // inference was sent, and neither the error nor its cause is propagated.
87
+ throw new JevCallError(undefined, "IDENTITY_UNAVAILABLE");
88
+ }
89
+ try {
90
+ const response = await (options.fetch ?? fetch)(base, {
91
+ method: "POST",
92
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
93
+ body: JSON.stringify(body),
94
+ redirect: "error",
95
+ signal: AbortSignal.timeout(timeoutMs)
96
+ });
97
+ if (!response.ok) {
98
+ // Avoid trusting arbitrary upstream messages or echoing request state.
99
+ throw new JevCallError(response.status, await safeErrorCode(response));
100
+ }
101
+ const value: unknown = await response.json();
102
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
103
+ throw new JevCallError(response.status, "INVALID_RESPONSE");
104
+ }
105
+ const result = value as JevResult;
106
+ if (typeof result.model !== "string" || !result.answers || typeof result.answers !== "object" || Array.isArray(result.answers)) {
107
+ throw new JevCallError(response.status, "INVALID_RESPONSE");
108
+ }
109
+ for (const name of Object.keys(body.questions)) {
110
+ if (!Object.hasOwn(result.answers, name)) {
111
+ throw new JevCallError(response.status, "MISSING_ANSWER");
112
+ }
113
+ }
114
+ return result;
115
+ } catch (error) {
116
+ if (error instanceof JevCallError) throw error;
117
+ // A transport/parse failure can happen after inference was billed.
118
+ throw new JevCallError(undefined, "RESULT_UNKNOWN");
119
+ }
120
+ }
121
+ };
122
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@numa-tech/numa",
3
- "version": "1.24.0",
3
+ "version": "1.26.0",
4
4
  "description": "Cross-platform CLI for the Numa internal developer platform with Keycloak authentication and MCP support.",
5
5
  "author": "numa-tech",
6
6
  "keywords": [
@@ -32,12 +32,14 @@
32
32
  "docs/kb-access.md",
33
33
  "docs/gitops-configuration.md",
34
34
  "docs/design/kb-cli.md",
35
+ "docs/design/kb-web-workspace.md",
35
36
  "scripts/export-kb-contract.py",
36
37
  "src/kb/openapi-contract.json",
37
38
  "docs/authorization-configuration-publication.md",
38
39
  "docs/oss-policy-client-governance.md",
39
40
  "docs/environment-configuration-publication.md",
40
- "docs/keycloak-managed-import.md"
41
+ "docs/keycloak-managed-import.md",
42
+ "docs/design/kb-capability-upgrade.md"
41
43
  ],
42
44
  "scripts": {
43
45
  "clean": "node scripts/clean.mjs",
@@ -47,9 +49,9 @@
47
49
  "root:typecheck": "tsc --noEmit",
48
50
  "test": "turbo run test root:test",
49
51
  "dev:oss-web": "tsx src/cli.ts oss web",
50
- "test:oss-web": "node --import tsx --test src/oss/web.test.ts src/oss/web-session.test.ts src/oss/web-transfers.test.ts src/oss/web-command.test.ts src/oss/local-picker.test.ts src/oss/transfer-paths.test.ts src/oss/transfer-worker.test.ts src/oss/transfer-queue.test.ts src/oss/web-page.test.ts src/oss/file-operations.test.ts src/oss/download-links.test.ts",
52
+ "test:oss-web": "node --import tsx --test src/oss/web.test.ts src/oss/web-session.test.ts src/oss/web-transfers.test.ts src/oss/web-command.test.ts src/oss/local-picker.test.ts src/oss/transfer-paths.test.ts src/oss/transfer-worker.test.ts src/oss/transfer-queue.test.ts src/oss/web-page.test.ts src/oss/file-operations.test.ts src/oss/download-links.test.ts src/local-web/context-menu.test.ts",
51
53
  "test:oss-auth-code": "node scripts/test-oss-auth-code.mjs",
52
- "root:test": "node --import tsx --test --test-concurrency=4 src/mcp-gateway/profile.test.ts src/mcp-gateway/runtime.test.ts src/mcp-gateway/install.test.ts src/branding.test.ts src/app-config.test.ts src/platform-profile.test.ts src/profile-migration.test.ts src/identity.test.ts src/backend.test.ts src/oauth.test.ts src/web-console.test.ts src/cli-options.test.ts src/mail/client.test.ts src/wecom/client.test.ts src/wecom/commands.test.ts src/wecom/tools.test.ts src/ai-registry/client.test.ts src/ai-registry/bootstrap.test.ts src/ai-registry/install-defaults.test.ts src/ai-registry/workspace.test.ts src/ai-registry/namespace.test.ts src/compatibility/registry-instance.test.ts src/ai-registry/sync.test.ts src/ai-registry/bridge.test.ts src/ai-registry/skill-installer.test.ts src/ai-registry/skill-uninstaller.test.ts src/ai-registry/skill-dependencies.test.ts src/ai-registry/updates.test.ts src/maintenance/scheduler.test.ts src/maintenance/codex.test.ts src/ai-registry/mcp-installer.test.ts src/media/client.test.ts src/media/commands.test.ts src/media/transfers.test.ts src/oss/client.test.ts src/oss/governance.test.ts src/oss/commands.test.ts src/oss/auth-code.test.ts src/oss/clipboard.test.ts src/oss/web.test.ts src/oss/web-session.test.ts src/oss/web-transfers.test.ts src/oss/web-command.test.ts src/oss/local-picker.test.ts src/oss/transfer-paths.test.ts src/oss/transfer-worker.test.ts src/oss/transfer-queue.test.ts src/oss/web-page.test.ts src/oss/file-operations.test.ts src/oss/download-links.test.ts src/oss/bootstrap.test.ts src/oss/transfers.test.ts src/oss/search.test.ts src/oss/tools.test.ts src/mcp/install.test.ts src/mcp/catalog.test.ts src/tasks/runtime.test.ts src/tasks/stdio.test.ts src/tasks/wecom-notify.test.ts src/tasks/oss-query.test.ts src/interactive/wizard.test.ts src/pipeline/client.test.ts src/pipeline/commands.test.ts src/gitops/client.test.ts src/gitops/commands.test.ts src/gitops/workload-input.test.ts src/gitops/workload-client.test.ts src/gitops/workload-commands.test.ts src/repositories/client.test.ts src/repositories/commands.test.ts src/publications/scanner.test.ts src/publications/client.test.ts src/publications/commands.test.ts src/jenkins-jobs/client.test.ts src/jenkins-jobs/commands.test.ts src/clusters/client.test.ts src/clusters/commands.test.ts src/clusters/kubeconfig-discovery.test.ts src/teams/client.test.ts src/teams/commands.test.ts src/authorization/bootstrap-client.test.ts src/authorization/commands.test.ts src/authorization/access-request-client.test.ts src/authorization/access-request-commands.test.ts src/application-candidates/client.test.ts src/application-candidates/commands.test.ts src/application-onboarding/client.test.ts src/application-onboarding/commands.test.ts src/application-onboarding/tui.test.ts src/chatgpt-desktop.test.ts src/codex-integration.test.ts scripts/release-all.test.mjs src/authorization/enrollment.test.ts src/authorization/application-commands.test.ts src/authorization/application-client.test.ts src/authorization/application-schemas.test.ts src/authorization/enrollment-client.test.ts src/notion/client.test.ts src/notion/commands.test.ts src/notion/tools.test.ts src/notion/machine-cli.test.ts src/cloud/accounts.test.ts src/cloud/bootstrap.test.ts src/cloud/business-bootstrap.test.ts src/cloud/maintenance.test.ts src/cloud/wizard.test.ts src/cloud/oss-bootstrap.test.ts src/business-issuer.test.ts src/login-configuration.test.ts src/login-recovery.test.ts src/ai-registry/mcp-management.test.ts src/ai-registry/mcp-runtime.test.ts src/ai-registry/mcp-stdio.test.ts src/kb/client.test.ts src/kb/config.test.ts src/kb/commands.test.ts src/kb/login.test.ts src/kb/transfers.test.ts src/kb/contract.test.ts src/authorization/environment-governance-client.test.ts src/authorization/environment-governance-commands.test.ts src/authorization/environment-publication.test.ts src/authorization/managed-keycloak.test.ts src/authorization/managed-keycloak-producer.test.ts",
54
+ "root:test": "node --import tsx --test --test-concurrency=4 src/mcp-gateway/profile.test.ts src/mcp-gateway/runtime.test.ts src/mcp-gateway/install.test.ts src/branding.test.ts src/app-config.test.ts src/platform-profile.test.ts src/profile-migration.test.ts src/identity.test.ts src/backend.test.ts src/oauth.test.ts src/web-console.test.ts src/cli-options.test.ts src/mail/client.test.ts src/wecom/client.test.ts src/wecom/commands.test.ts src/wecom/tools.test.ts src/ai-registry/client.test.ts src/ai-registry/bootstrap.test.ts src/ai-registry/install-defaults.test.ts src/ai-registry/workspace.test.ts src/ai-registry/namespace.test.ts src/compatibility/registry-instance.test.ts src/ai-registry/sync.test.ts src/ai-registry/bridge.test.ts src/ai-registry/skill-installer.test.ts src/ai-registry/skill-uninstaller.test.ts src/ai-registry/skill-dependencies.test.ts src/ai-registry/updates.test.ts src/maintenance/scheduler.test.ts src/maintenance/codex.test.ts src/ai-registry/mcp-installer.test.ts src/media/client.test.ts src/media/commands.test.ts src/media/transfers.test.ts src/oss/client.test.ts src/oss/governance.test.ts src/oss/commands.test.ts src/oss/auth-code.test.ts src/oss/clipboard.test.ts src/oss/web.test.ts src/oss/web-session.test.ts src/oss/web-transfers.test.ts src/oss/web-command.test.ts src/oss/local-picker.test.ts src/oss/transfer-paths.test.ts src/oss/transfer-worker.test.ts src/oss/transfer-queue.test.ts src/oss/web-page.test.ts src/oss/file-operations.test.ts src/oss/download-links.test.ts src/oss/bootstrap.test.ts src/oss/transfers.test.ts src/oss/search.test.ts src/oss/tools.test.ts src/mcp/install.test.ts src/mcp/catalog.test.ts src/tasks/runtime.test.ts src/tasks/stdio.test.ts src/tasks/wecom-notify.test.ts src/tasks/oss-query.test.ts src/interactive/wizard.test.ts src/pipeline/client.test.ts src/pipeline/commands.test.ts src/gitops/client.test.ts src/gitops/commands.test.ts src/gitops/workload-input.test.ts src/gitops/workload-client.test.ts src/gitops/workload-commands.test.ts src/repositories/client.test.ts src/repositories/commands.test.ts src/publications/scanner.test.ts src/publications/client.test.ts src/publications/commands.test.ts src/jenkins-jobs/client.test.ts src/jenkins-jobs/commands.test.ts src/clusters/client.test.ts src/clusters/commands.test.ts src/clusters/kubeconfig-discovery.test.ts src/teams/client.test.ts src/teams/commands.test.ts src/authorization/bootstrap-client.test.ts src/authorization/commands.test.ts src/authorization/access-request-client.test.ts src/authorization/access-request-commands.test.ts src/application-candidates/client.test.ts src/application-candidates/commands.test.ts src/application-onboarding/client.test.ts src/application-onboarding/commands.test.ts src/application-onboarding/tui.test.ts src/chatgpt-desktop.test.ts src/codex-integration.test.ts scripts/release-all.test.mjs src/authorization/enrollment.test.ts src/authorization/application-commands.test.ts src/authorization/application-client.test.ts src/authorization/application-schemas.test.ts src/authorization/enrollment-client.test.ts src/notion/client.test.ts src/notion/commands.test.ts src/notion/tools.test.ts src/notion/machine-cli.test.ts src/cloud/accounts.test.ts src/cloud/bootstrap.test.ts src/cloud/business-bootstrap.test.ts src/cloud/maintenance.test.ts src/cloud/wizard.test.ts src/cloud/oss-bootstrap.test.ts src/business-issuer.test.ts src/login-configuration.test.ts src/login-recovery.test.ts src/ai-registry/mcp-management.test.ts src/ai-registry/mcp-runtime.test.ts src/ai-registry/mcp-stdio.test.ts src/kb/client.test.ts src/kb/config.test.ts src/kb/commands.test.ts src/kb/login.test.ts src/kb/transfers.test.ts src/kb/contract.test.ts src/authorization/environment-governance-client.test.ts src/authorization/environment-governance-commands.test.ts src/authorization/environment-publication.test.ts src/authorization/managed-keycloak.test.ts src/authorization/managed-keycloak-producer.test.ts src/kb/web.test.ts src/kb/web-queue.test.ts src/local-web/context-menu.test.ts src/kb/web-login.test.ts src/kb/capabilities.test.ts",
53
55
  "pack:check": "turbo run pack:check root:pack:check",
54
56
  "root:pack:check": "node scripts/check-root-package.mjs",
55
57
  "boundaries": "node scripts/check-workspace-boundaries.mjs",
@@ -67,7 +69,7 @@
67
69
  "release:resume": "node scripts/release-all.mjs --resume",
68
70
  "release:promote": "node scripts/release-all.mjs --promote",
69
71
  "prepack": "npm run root:build && npm run root:pack:check",
70
- "test:kb": "node --import tsx --test --test-concurrency=4 src/kb/client.test.ts src/kb/config.test.ts src/kb/commands.test.ts src/kb/login.test.ts src/kb/transfers.test.ts src/kb/contract.test.ts"
72
+ "test:kb": "node --import tsx --test --test-concurrency=4 src/kb/client.test.ts src/kb/config.test.ts src/kb/commands.test.ts src/kb/login.test.ts src/kb/transfers.test.ts src/kb/contract.test.ts src/kb/web.test.ts src/kb/web-queue.test.ts src/local-web/context-menu.test.ts src/kb/web-login.test.ts src/kb/capabilities.test.ts"
71
73
  },
72
74
  "engines": {
73
75
  "node": ">=20.12.0"
@@ -75,8 +77,8 @@
75
77
  "dependencies": {
76
78
  "@clack/prompts": "^1.7.0",
77
79
  "@modelcontextprotocol/sdk": "^1.29.0",
78
- "@numa-tech/cli-auth": "1.24.0",
79
- "@tokensrc/codex": "1.24.0",
80
+ "@numa-tech/cli-auth": "1.26.0",
81
+ "@tokensrc/codex": "1.26.0",
80
82
  "ali-oss": "^6.23.0",
81
83
  "commander": "^14.0.3",
82
84
  "ink": "^6.8.0",