@blade-hq/agent-client 2612.0.0-beta.6 → 2612.0.0-beta.61

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 (94) hide show
  1. package/README.md +321 -35
  2. package/dist/application-client.d.ts +19 -0
  3. package/dist/blade-client.d.ts +23 -9
  4. package/dist/{chunk-KLI47WAU.js → chunk-IMWLNQ2Y.js} +1513 -377
  5. package/dist/chunk-IMWLNQ2Y.js.map +1 -0
  6. package/dist/index.d.ts +15 -5
  7. package/dist/index.js +15 -1
  8. package/dist/internal.d.ts +9 -3
  9. package/dist/internal.js +27 -3
  10. package/dist/internal.js.map +1 -1
  11. package/dist/resources/auth.d.ts +9 -1
  12. package/dist/resources/computers.d.ts +2 -7
  13. package/dist/resources/connectors.d.ts +307 -0
  14. package/dist/resources/headless.d.ts +1 -1
  15. package/dist/resources/models.d.ts +6 -1
  16. package/dist/resources/phones.d.ts +109 -0
  17. package/dist/resources/plugins.d.ts +1 -0
  18. package/dist/resources/sandbox.d.ts +22 -0
  19. package/dist/resources/sessions.d.ts +75 -10
  20. package/dist/schemas/ask-user-question.d.ts +21 -0
  21. package/dist/schemas/context-attachments.d.ts +18 -0
  22. package/dist/schemas/message-utils.d.ts +3 -9
  23. package/dist/schemas/message.d.ts +15 -1
  24. package/dist/schemas/plugin-refs.d.ts +7 -0
  25. package/dist/schemas/projection.d.ts +14 -1
  26. package/dist/schemas/session.d.ts +2 -48
  27. package/dist/server-clock.d.ts +2 -0
  28. package/dist/session/agent-session.d.ts +44 -97
  29. package/dist/session/definition.d.ts +3 -0
  30. package/dist/session/events.d.ts +0 -1
  31. package/dist/session/hub.d.ts +0 -24
  32. package/dist/session/state.d.ts +7 -1
  33. package/dist/session/workflow.d.ts +10 -3
  34. package/dist/shared/projection/builder.d.ts +0 -2
  35. package/dist/shared/projection/index.d.ts +1 -0
  36. package/dist/shared/projection/workflow.d.ts +24 -0
  37. package/dist/tool-provider.d.ts +55 -0
  38. package/dist/types/socket-events.d.ts +167 -13
  39. package/dist/version.d.ts +0 -1
  40. package/node_modules/engine.io-client/build/cjs/globals.node.d.ts +2 -3
  41. package/node_modules/engine.io-client/build/cjs/index.d.ts +3 -3
  42. package/node_modules/engine.io-client/build/cjs/index.js +1 -4
  43. package/node_modules/engine.io-client/build/cjs/socket.d.ts +39 -82
  44. package/node_modules/engine.io-client/build/cjs/socket.js +151 -218
  45. package/node_modules/engine.io-client/build/cjs/transport.d.ts +1 -1
  46. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.d.ts +4 -4
  47. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.js +2 -2
  48. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.node.js +17 -7
  49. package/node_modules/engine.io-client/build/cjs/transports/websocket.node.js +17 -7
  50. package/node_modules/engine.io-client/build/esm/globals.node.d.ts +2 -3
  51. package/node_modules/engine.io-client/build/esm/index.d.ts +3 -3
  52. package/node_modules/engine.io-client/build/esm/index.js +1 -2
  53. package/node_modules/engine.io-client/build/esm/socket.d.ts +39 -82
  54. package/node_modules/engine.io-client/build/esm/socket.js +141 -206
  55. package/node_modules/engine.io-client/build/esm/transport.d.ts +1 -1
  56. package/node_modules/engine.io-client/build/esm/transports/polling-xhr.d.ts +4 -4
  57. package/node_modules/engine.io-client/build/esm/transports/polling-xhr.js +2 -2
  58. package/node_modules/engine.io-client/build/esm-debug/globals.node.d.ts +2 -3
  59. package/node_modules/engine.io-client/build/esm-debug/index.d.ts +3 -3
  60. package/node_modules/engine.io-client/build/esm-debug/index.js +1 -2
  61. package/node_modules/engine.io-client/build/esm-debug/socket.d.ts +39 -82
  62. package/node_modules/engine.io-client/build/esm-debug/socket.js +150 -215
  63. package/node_modules/engine.io-client/build/esm-debug/transport.d.ts +1 -1
  64. package/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.d.ts +4 -4
  65. package/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.js +2 -2
  66. package/node_modules/engine.io-client/dist/engine.io.esm.min.js +3 -3
  67. package/node_modules/engine.io-client/dist/engine.io.esm.min.js.map +1 -1
  68. package/node_modules/engine.io-client/dist/engine.io.js +164 -239
  69. package/node_modules/engine.io-client/dist/engine.io.js.map +1 -1
  70. package/node_modules/engine.io-client/dist/engine.io.min.js +3 -3
  71. package/node_modules/engine.io-client/dist/engine.io.min.js.map +1 -1
  72. package/node_modules/engine.io-client/package.json +2 -2
  73. package/node_modules/socket.io-parser/build/cjs/binary.js +4 -1
  74. package/node_modules/socket.io-parser/build/cjs/index.d.ts +14 -4
  75. package/node_modules/socket.io-parser/build/cjs/index.js +18 -11
  76. package/node_modules/socket.io-parser/build/esm/binary.js +4 -1
  77. package/node_modules/socket.io-parser/build/esm/index.d.ts +14 -4
  78. package/node_modules/socket.io-parser/build/esm/index.js +14 -10
  79. package/node_modules/socket.io-parser/build/esm-debug/binary.js +4 -1
  80. package/node_modules/socket.io-parser/build/esm-debug/index.d.ts +14 -4
  81. package/node_modules/socket.io-parser/build/esm-debug/index.js +14 -10
  82. package/node_modules/socket.io-parser/package.json +1 -1
  83. package/node_modules/ws/index.js +15 -6
  84. package/node_modules/ws/lib/constants.js +1 -0
  85. package/node_modules/ws/lib/permessage-deflate.js +9 -7
  86. package/node_modules/ws/lib/receiver.js +37 -0
  87. package/node_modules/ws/lib/sender.js +6 -1
  88. package/node_modules/ws/lib/websocket-server.js +18 -6
  89. package/node_modules/ws/lib/websocket.js +50 -17
  90. package/node_modules/ws/package.json +8 -3
  91. package/node_modules/ws/wrapper.mjs +14 -1
  92. package/package.json +4 -4
  93. package/public-api.md +604 -37
  94. package/dist/chunk-KLI47WAU.js.map +0 -1
package/README.md CHANGED
@@ -111,6 +111,10 @@ const configured = await client.sessions.create({ intent: "数据分析", model:
111
111
 
112
112
  两者都返回 [`AgentSession`](#agentsession)。
113
113
 
114
+ SDK 创建的会话默认归接入应用(`initiator: "sdk"`),运行事件仍由 SDK 交付,提醒展示和点击导航由接入应用自行处理,BA 内置 Web 不弹出这些会话的完成或等待回复提醒。内置 Web 创建入口显式使用 `initiator: "ui"`;此字段表示应用归属,与 headless 执行模式无关。
115
+
116
+ 创建请求省略默认的 `sdk` 值,由服务端采用默认归属;显式 `ui` 仍会发送。这样默认创建不会因旧服务端尚未定义 `initiator` 而返回 422。接入时仍须按服务端 `/api/version` 返回的 `version` 安装配套 SDK,其他新功能参数不保证适用于旧服务端。
117
+
114
118
  ### 声明完整会话
115
119
 
116
120
  自定义 Solution、Skill 和模型无需手写上传步骤:
@@ -151,6 +155,7 @@ await client.sessions.getSession(id) // 会话详情
151
155
  await client.sessions.updateSession(id, { intent: "新标题" })
152
156
  await client.sessions.deleteSession(id)
153
157
  await client.sessions.getSessionTurns(id) // 历史消息(投影格式,与实时流同构)
158
+ await client.sessions.getRunResult(id, executionId) // 所有者查询持久执行结果;尚无结束记录返回 null
154
159
  const page = await client.sessions.getSessionTurnsPage(id) // 聊天首屏 recent page
155
160
  await client.sessions.getSessionTurnsPage(id, { before: page.nextBefore! })
156
161
  // 从某条用户发言定位到最新(消息导航点窗口外的发言):调用参数是 SessionTurnsQuery
@@ -160,9 +165,17 @@ const index = await client.sessions.getSessionTurnIndex(id) // SessionTurnIndex
160
165
  const first = index.items[0] // SessionTurnIndexItem
161
166
  ```
162
167
 
163
- 创建会话时可以绑定 Streamable HTTP MCP 来源;BA 会在创建成功前读取并保存工具列表,
164
- 来源此时必须可访问且接受创建请求中的 Bearer 凭据。之后工具列表在该会话中固定,
165
- 来源默认按 lazy 模式提供,只有列在 `eager_tools` 的工具直接进入模型函数列表。
168
+ 创建会话时可以绑定 Streamable HTTP MCP 来源,并用可选 `description` 提供用途说明。
169
+ 默认 `exposure: "lazy"` 只保存来源配置,创建时不连接 MCP;模型初始只看到来源名称和说明。
170
+ 通过 `lazy_tool_search({query: "所需能力", limit: 8})` 全局搜索并获得命中工具的完整标识、
171
+ 说明和参数 schema,再用 `lazy_tool_run` 调用。
172
+ 首次搜索发现工具,同一执行内复用成功目录;下一次执行重新发现。失败不缓存,也不返回部分成功。
173
+
174
+ `exposure: "eager"` 以及非空 `eager_tools` 保持创建时发现并保存目录:eager 将 `include_tools`
175
+ 允许的工具和完整 schema 直接加入执行模式的模型工具列表;`eager_tools` 可在 lazy 来源中选出
176
+ 直接暴露的工具,也可指定 eager 来源中的优先工具。主运行和子智能体使用相同策略,计划模式不加载。
177
+ 每个运行最多直接加载 32 个工具、64 KiB 完整工具定义;超预算的工具仍可通过搜索后 run 使用。
178
+ 显式会话 refresh 只重新发现 eager 来源目录。调用始终经 Go MCP dispatcher,继续校验当前身份和工具权限。
166
179
  Bearer 凭据只在创建或轮换时提交,查询不会返回它:
167
180
 
168
181
  ```ts
@@ -173,14 +186,65 @@ const { session_id } = await client.sessions.createSessionWithRequest({
173
186
  id: "feishu",
174
187
  url: "https://adapter.example.com/mcp",
175
188
  bearer_token: sessionToken,
176
- eager_tools: ["send_files"],
189
+ include_tools: ["send_files"],
177
190
  }],
178
191
  })
179
192
  ```
180
193
 
181
- 工具集合由创建请求确定,后续 `session.send()` 沿用它。查询来源和轮换凭据的 REST 接口见 Swagger;轮换时传入 `null` 可撤销某来源凭据。
194
+ 来源与白名单由创建或配置升级请求确定。查询来源和轮换凭据的 REST 接口见 Swagger;轮换时传入 `null` 可撤销某来源凭据。
182
195
  `skip_confirmations: true` 会让该会话的普通轮次直接执行工具,适合由接入方自行承接交互的渠道会话。
183
196
 
197
+ ### 升级已有会话的全套配置
198
+
199
+ 会话所有者可通过 `GET/PATCH /api/sessions/{id}/configuration` 查询和升级已有沙盒会话,
200
+ 保留 ID、对话历史、压缩摘要、用户文件及非方案技能。升级不重跑初始化或初始消息。
201
+ 请求类型为 `UpdateSessionConfigurationRequest`,查询和升级返回 `SessionConfiguration`。
202
+
203
+ ```ts
204
+ const current = await client.sessions.getSessionConfiguration(id)
205
+ const applied = await client.sessions.updateSessionConfiguration(id, {
206
+ version: "pa-2026-10-12",
207
+ expected_revision: current.revision,
208
+ session_solution_asset_id: newSolutionAssetId,
209
+ biz_role_id: "main",
210
+ model: "your-model-id",
211
+ enable_thinking: false,
212
+ memory_enabled: true,
213
+ disable_tools: [],
214
+ env: {},
215
+ skip_confirmations: true,
216
+ compaction_ratio: 0.75,
217
+ mcp_sources: [{
218
+ id: "personal-assistant",
219
+ url: "https://adapter.example.com/mcp",
220
+ include_tools: ["send_user_message", "delegate_task"],
221
+ }],
222
+ })
223
+ console.log(applied.version, applied.revision)
224
+ ```
225
+
226
+ 创建和配置升级均可直接提交 `session_solution_files: [{ path, content }]`(UTF-8)。
227
+ 服务端负责暂存、校验、安装与清理,不需要额外上传/删除请求,也不占显式上传配额。
228
+ Solution 来源在 `solution_ref`、`solution_id`、`session_solution_asset_id` 和 `session_solution_files` 中选一个;只更新
229
+ `biz_role_id` 时使用当前方案。其他可升级字段如示例。省略字段保持旧值;数组和对象整体
230
+ 替换,`[]`/`{}` 清空。只有 `enable_thinking`、`compaction_ratio` 接受 null,表示恢复默认。
231
+ 角色省略时保留原角色,新方案不包含它则报错。MCP 同 ID 的来源省略 bearer_token 保留旧
232
+ 凭据;新来源省略则匿名访问,显式 null 也表示匿名访问。已撤销来源必须显式提供凭据。
233
+ 被删除的来源和被移出白名单的工具不能继续调用,查询不返回凭据。
234
+
235
+ 全部校验(含 MCP 发现)成功后一起提交,失败继续使用旧配置。执行、压缩或轮次交接中
236
+ 返回 409,请在轮次结束后重试;成功后下一轮生效。同一 version 同一请求重复提交安全且
237
+ revision 不增加,同 version 不同请求返回 409。`expected_revision` 可选,用于避免覆盖
238
+ 另一份升级。首次查询未升级的会话返回 version=null、revision=0;version/revision 记录
239
+ 本接口成功提交的升级,其他既有独立修改接口仍可使用,实际字段以查询的 configuration 为准。
240
+
241
+ 配置升级只追加当前规则快照或撤回说明,不改写或过滤已有模型消息,保持对话消息前缀。
242
+ 更换模型或修改 eager 工具 schema 时允许缓存失效。
243
+
244
+ 回放会话不支持该接口。父子
245
+ 关系、聊天分组、template_id、primary_skill_id 等创建或绑定字段提交后返回 422,未知字段
246
+ 也返回 422。方案技能与用户已有技能同路径冲突时拒绝升级,用户文件不会被覆盖。
247
+
184
248
  `SessionTurnsQuery` 的 `before` 与 `fromEntryId` 互斥;定位命中不了当前分支时服务端返回
185
249
  404 / 409,而不是空窗口。目录的 `entryId` 与 `/turns` 的 `turn_id` 同源,可直接当 `before` 游标。
186
250
 
@@ -372,27 +436,67 @@ const body = { model: defaultServiceModel, messages, stream: true }
372
436
  ### 远程电脑:client.computers
373
437
 
374
438
  一个会话除了自己的运行时,还可以操作用户接入的其他电脑(`blade daemon connect` 接进来的机器)。
375
- **默认一台都不能用**,要先为这次会话启动它。
439
+ **用户接入的电脑默认在本次会话可用**,可以按会话停用。
376
440
 
377
441
  ```ts
378
442
  const { computers } = await client.computers.list(sessionId)
379
443
  // SessionComputerList → { computers: SessionComputer[] }
380
444
  // SessionComputer: { id, label, os, arch, home, workspace, allowed_paths,
381
- // online, enabled, is_primary, last_seen_at, ... }
445
+ // online, enabled, last_seen_at, ... }
382
446
 
383
447
  await client.computers.setEnabled(sessionId, computer.id, true) // 启动
384
448
  await client.computers.setEnabled(sessionId, computer.id, false) // 停用
385
449
  ```
386
450
 
387
- `online` 和 `enabled` 是**两件独立的事**:前者指那台电脑此刻连着后端,后者指这次会话已经启动了它。
451
+ `online` 和 `enabled` 是**两件独立的事**:前者指那台电脑此刻连着后端,后者指这次会话允许使用它(默认启用,显式停用优先)。
388
452
  离线的电脑也能先启动,等它连上就直接可用。`online` 由服务端读心跳时间判定,不做实时探测,
389
453
  所以电脑离线时列表照样返回它——你拿得到「它离线了」这个结论。
390
454
 
391
- `is_primary` 表示这次会话本身就跑在这台电脑上。这种电脑恒为可用,也不能停用——
392
- 那是会话自己的工作目录所在。
455
+ 智能体与会话工作目录始终在沙盒,远程电脑只提供命令、文件与画面能力。
393
456
 
394
457
  `ComputersResource` 是 `client.computers` 的类型。
395
458
 
459
+ ### 手机:client.phones
460
+
461
+ 用户接进来的 Android 手机。手机在这台工作环境里就是一台 adb 设备,智能体可以直接
462
+ 用 `adb` 命令操作它。和一键可用的电脑不同,手机要先在设置页拿到一把**接入密钥**
463
+ 填进手机 App,App 连上来之后设备自动登记。
464
+
465
+ ```ts
466
+ const { phones } = await client.phones.listAccount()
467
+ // PhoneList → { phones: PhoneDevice[] }
468
+ // PhoneDevice: { id, label, serial, model, android_version, created_at,
469
+ // last_seen_at, online, adb_online, enabled, session_id }
470
+
471
+ await client.phones.setEnabled(sessionId, phone.id, true) // 本次会话可以用它
472
+ await client.phones.setEnabled(sessionId, phone.id, false) // 停用
473
+ ```
474
+
475
+ `online` 和 `adb_online` 是**两件独立的事**,会单独坏:
476
+
477
+ - `online`:手机此刻连着后端。连上就算,不等沙盒。
478
+ - `adb_online`:沙盒里的 adb 此刻能对它执行命令。需要 adb 认证通过。
479
+
480
+ 手机连着但沙盒里没装 adb、或者认证没过,`online` 仍然是 true 而 `adb_online` 是
481
+ false。合成一个「离线」会让人去排查网络,而问题在沙盒。
482
+
483
+ `PhoneState` 是把这两个字段折成 UI 状态的唯一出处(`phoneState(phone)`),不要
484
+ 在调用方重新拼一遍判断。
485
+
486
+ ```ts
487
+ const key = await client.phones.getKey()
488
+ // PhoneAccessKey → { key, hub_url, connector_url }
489
+
490
+ await client.phones.resetKey() // 换一把新密钥并作废旧密钥
491
+ await client.phones.revokeKey() // 作废,之后手机重连一律被拒
492
+ ```
493
+
494
+ `key` 是以 `pk_` 开头的长期凭据,没作废就一直有效。`hub_url` 是服务器地址(协议+
495
+ 主机+端口),`connector_url` 是手机 App 的下载地址,管理员可以配成自己的静态文件。
496
+
497
+ `PhonesResource` 是 `client.phones` 的类型,`PhoneDevice` / `PhoneAccessKey` /
498
+ `PhoneList` / `PhoneHealth` / `PhonePipeline` / `PhoneState` 是配套的公开类型。
499
+
396
500
  ## AgentSession
397
501
 
398
502
  一个会话的实时状态机。**状态归属实例**:同一页面建多个会话互不干扰。
@@ -475,7 +579,7 @@ await chat.attachApp({
475
579
  })
476
580
  ```
477
581
 
478
- `getContext` 在每次 `send()` 前执行一次,适合路由、当前选中对象等轻量页面状态;它不做响应式依赖追踪。完整业务数据由开发者自己的 CLI 查询。`command` 是智能体在 Bash 中直接执行的命令名,不会套在 `blade` 命令下。
582
+ `getContext` 在启动新一轮的 `send()` 前执行一次;运行中的引导和排队消息沿用当前运行的应用上下文,不重复采集。上下文适合承载路由、当前选中对象等轻量页面状态,不做响应式依赖追踪。完整业务数据由开发者自己的 CLI 查询。`command` 是智能体在 Bash 中直接执行的命令名,不会套在 `blade` 命令下。
479
583
 
480
584
  `runtime: "python"` 使用沙盒系统 Python 运行单文件脚本;`runtime: "executable"` 接受能在 Linux 沙盒运行的可执行文件。`source` 可传 `Blob`、`File` 或浏览器可读取的 URL。应用 CLI 安装成功后随会话工作区保留;运行时可通过 `BLADE_APP_CONTEXT_FILE` 读取同一份页面上下文 JSON。
481
585
 
@@ -560,11 +664,12 @@ await client.headless.run("统计上月订单量", { schema })
560
664
 
561
665
  ## 版本
562
666
 
563
- Git Release Tag 是唯一版本来源:发布 `v1.2.0` 时,镜像和两个 NPM 包均为
564
- `1.2.0`,SDK 在 Socket.IO 握手中也声明 `1.2.0`。应用代码不需要处理版本。
667
+ 接入目标环境前读取公开的 `GET /api/version`,按返回版本安装并钉住 SDK;
668
+ NPM 的默认分发版本可能落后于目标 Server。
565
669
 
566
- 只有一个兼容性断点:Server `1.1.1` 及以上要求
567
- `@blade-hq/agent-client` `1.1.1` 及以上;未声明 SDK 身份或版本的旧客户端也会被拒绝。
670
+ Server 不因 SDK 身份或版本拒绝 Socket.IO 连接。该兼容边界统一见
671
+ [仓库指引](../../../AGENTS.md#兼容性)。
672
+ 发布规则见 [release Skill](../../../.claude/skills/release/SKILL.md)。
568
673
 
569
674
  ## 自己渲染消息
570
675
 
@@ -613,28 +718,17 @@ function Message({ message }: { message: ChatMessage }) {
613
718
  }
614
719
  ```
615
720
 
616
- ### 让用户挑一个技能来做事
617
-
618
- 自己实现技能选择器时,用 `transformSlashCommand` 把选中的技能翻译成智能体能执行的一段话:
721
+ ### 在消息中引用技能
619
722
 
620
- ```ts
621
- import { transformSlashCommand, type SkillMentionAvailability } from "@blade-hq/agent-client"
622
-
623
- transformSlashCommand("org/data-analysis", "分析这份季度报表")
624
- // "请使用 org/data-analysis skill 完成任务\n分析这份季度报表"
625
- ```
626
-
627
- 第三个参数说明这个技能眼下能不能直接用。技能装没装、加载没加载,用户不需要知道,但智能体需要——传进来之后它会被翻译成对应的 CLI 步骤:
723
+ `SkillRef` 表示技能身份,`MessageRef` 是技能与插件引用的联合类型;`normalizeMessageRefs` 可校验和去重引用。自定义技能选择器把技能身份放进 `refs`,正文保留用户输入。运行时另行提供技能描述和文件路径,智能体根据原文判断解释、比较还是使用。
628
724
 
629
725
  ```ts
630
- // Blade Hub 上已安装、但当前会话还没加载:要求先 use
631
- transformSlashCommand(skillId, prompt, { local: false, installed: true })
632
-
633
- // Blade Hub 上还没安装:要求先 install 再 use
634
- transformSlashCommand(skillId, prompt, { local: false, installed: false })
726
+ session.send("@org/data-analysis 这是什么?", {
727
+ refs: [{ provider: "skill", id: "org/data-analysis", label: "org/data-analysis" }],
728
+ })
635
729
  ```
636
730
 
637
- 不传第三个参数时按本地已有处理,和不带这个参数的老用法结果一致。
731
+ 技能引用仅属于本条消息。`transformSlashCommand` 只把 `<skill>名称</skill>` 标记转换为 `@名称`,不会增加执行或安装指令;调用方仍需单独发送 `refs`。
638
732
 
639
733
  ## 运行错误说明:userFacingErrorText
640
734
 
@@ -678,12 +772,28 @@ chat.renderers = {
678
772
 
679
773
  挂载节点位于 `<blade-chat>` 元素自己的子节点里(通过 `<slot>` 显示在对应位置),所以页面上的普通样式对它生效。可定制的区块与数据形状见 `BladeChatMountRenderers`(`toolCall`、`userMessage`、`assistantText`);相关类型:`MountRenderer`、`MountRendererObject`、`MountResult`、`MountHandle`。Vue 项目用 `@blade-hq/agent-vue` 的 `defineMountRenderer` 直接包装 Vue 组件。
680
774
 
775
+ ## 账号连接器
776
+
777
+ 内置飞书返回 `authorization.type="device"` 时,使用 `connectors.authorization(name)` 读取 `ConnectorAuthorization`,以 `connectors.authorize(name, action, pushUrl?, options?)` 推进授权。`ConnectorAuthorizationAction` 的 `authorize` 由 SW 按当前状态推进应用注册、核验与本人授权,`poll` 继续当前步骤,客户端只展示 `current_step` 的当前操作;应用注册、本人读取、取消和核验入口仍保留。`activation_pending=true` 表示新连接待首次完成,刷新后仍应保留默认启用意图;成功 finish 或明确停用后变为 false,已有记录缺字段不推断为新连接。空槽位返回 false,实际创建连接后的响应再变为 true。显式 `finish` 提交 BA 可执行能力,`ConnectorAuthorizationOptions.enable=false` 可在增加能力时保持 BA 已停用;`bind`、`pause`、`resume` 独立管理 PA 接收订阅,不改变 BA 启用状态。SW 保存并刷新全部飞书凭据,BA 服务端以当前用户 PAT 访问 SW HTTP MCP;浏览器只读取展示链接和脱敏状态。详情见 [飞书 SW 接入设计](../../../docs/specs/261026-feishu-sw-mcp/design.md)。
778
+
779
+ `ConnectorDomain` 与 `ConnectorCapability.category/category_name` 提供业务分类;`operations` 对每个 `tool_name` 分别给出有效权限 `status`、实现状态 `executable` 和实际动作 `effect`,`capability_id` 对应授权选择。`ConnectorApprovalStatus` 区分 `exempt`、`review_required` 与 `unknown`,必须结合 `approval_evidence` 展示,未知不能当作免审核。`ConnectorPermission.approval_status` 提供同样的结构化判断,原 `approval` 保留平台说明。`catalog_revision` 是业务目录内容版本;新增工具采用 SW 的实际 MCP schema,BA 服务端固定连接身份,平台权限和资源范围仍逐次校验。`ConnectorAuthorizationStep` 和 `ConnectorAuthorizationFailure` 提供当前步骤、可重试性、下一步与排查编号;部分失败不会清除已有授权。`connectors.authorizationPlan(name, capabilities)` 返回 `ConnectorAuthorizationPlan`,仅预览本次权限差异,并分别返回已就绪、需要本人授权、管理员审核、审批未知和不支持的能力集合;`connectors.diagnostics(name)` 返回 `ConnectorDiagnostics`,其中 `ConnectorAuditEvent` 是脱敏审计记录,`ConnectorRuntimeDiagnostics` 描述消息通道和 PA 投递的实际记录。`subscription_state` 分别表示未绑定、启用、暂停或失效,不能只用扫码成功判断收消息链路。
780
+
781
+ `ConnectorArtifact` 描述 SW 按用户、连接和应用隔离的临时文件,包含 SHA256、大小、来源与到期时间。`connectors.uploadFile(name, blob, fileName, requestId)` 把明确选择的附件经 BA 同源代理上传,重试复用原编号;`downloadFile(name, artifactId)` 返回经过 BA 鉴权的 Blob,不能把私有 `download_path` 当作公开分享链接。用户 PAT 只在 BA→SW 传输,不进入文件工具参数或浏览器响应。智能体可沿现有 `lazy_tool_run` 的 `files.read` 传一个当前会话相对文件,由平台安全暂存并注入 `artifact_id`;下载工具可用 `files.write` 把经过大小、SHA256 校验的产物保存到会话 `plugin-output`。不接受任意主机路径或远程下载地址,失败不发布输出文件。
782
+
783
+ 飞书的 `disconnect(name)` 仅停用 BA 绑定,保留 SW 账号与 PA 订阅。重新启用通过显式 `finish` 动作完成;停止推送可用 `pause`;移除连接并重新关联使用 `authorize(name, "remove", undefined, { expected_connection_id: view.connection_id })`。仅 SW 确认移除后清空旧绑定和目录,保留当前用户 PAT;不会删除飞书后台应用或自动发起新关联。成功后的目录暂不可读仍返回 disconnected 与 `catalog_unavailable=true`,不恢复旧应用。
784
+
785
+ `client.connectors.list()` 列出当前用户沙盒中创建的连接器及随项目发布的内置连接器,来源分别为 `personal`、`builtin`,无需商店安装;`listForSession(sessionId)` 同时返回当前对话的启用状态。`read(name)` 获取认证表单与连接状态;`connect(name, update)` 保存个人配置并执行真实认证;`verify(name)` 验证已有连接;`disconnect(name)` 清除普通连接器的连接和个人配置(飞书仅停用 BA)。每用户每连接器只有一个连接,修改账号更新原连接;密码与私有状态不通过读取接口返回。认证通过后,用 `sessions.setSessionPluginActivation(sessionId, name, true)` 在沙盒对话中启用;包内容变化后需要重新连接。
786
+
787
+ 相关类型:`ConnectorsResource`、`AccountConnector`、`ConnectorConnection`、`ConnectorConnectionView`。
788
+
681
789
  ## 第一方内部入口
682
790
 
683
791
  `@blade-hq/agent-client/internal` 是内置 Web、浏览器插件与 `@blade-hq/agent-react` 共用的内部件(状态机细节、判定逻辑、低层通道)。它随包发布,但**不属于公开面**:不写进本文档与 `public-api.md`,版本之间可以随时改动或删除。接入方只用主入口。
684
792
 
685
793
  ## 附录:公开类型索引
686
794
 
795
+ 已有会话可调用 `await session.selectModel("模型标识")` 立即切换模型,无须额外发送消息。父子任务后续模型调用沿用会话当前选择;正在调用的旧模型会取消,正在执行的业务工具保留。返回 `false` 表示选择失败,并通过会话的错误事件反馈原因。因模型调用失败停止的任务会接续执行;主动停止或等待用户回答的任务保持原状态。
796
+
687
797
  完整签名见 [public-api.md](./public-api.md)(由 `scripts/public-api-report.mjs` 生成并在 CI 校验)。
688
798
 
689
799
  - **客户端与登录**:`BladeClient`、`BladeClientOptions`、`UploadProgress`、`BladeApiError`、`LoginOptions`、`LoginResult`、`TokenStorageMode`、`AuthResource`、`ExchangeCodeParams`、`ExchangeCodeResult`、`ProvidersResponse`、`UserInfo`
@@ -692,11 +802,12 @@ chat.renderers = {
692
802
  - **声明式会话**:`SessionDefinition`、`SolutionDefinition`、`SkillDefinition`、`SkillFile`、`SessionConfig`、`TextFile`、`SessionSetupError`、`SessionSetupStage`
693
803
  - **会话消息队列**:`SessionQueueSnapshot`、`QueuedMessage`、`QueuedMessageStatus`、`QueueOperationResult`
694
804
  - **页面协作**:`CommandHandler`、`BladeChatMountRenderers`、`MountRenderer`、`MountRendererObject`、`MountResult`、`MountHandle`
695
- - **会话资源(REST)**:`SessionsResource`、`CreateSessionRequest`、`McpSourceConfig`、`PaginatedSessionsResult`、`SessionInfo`、`SessionStatus`、`SessionDetail`、`DEFAULT_REPLAY_SPEED`、`ReplaySpeed`、`ReplayState`、`SessionTurnsPage`、`SessionTurnsQuery`、`FileEntry`、`UploadFileEntry`、`UploadFilesOptions`、`AppCliDefinition`、`AppCliAttachment`
805
+ - **会话资源(REST)**:`SessionsResource`、`CreateSessionRequest`、`McpSourceConfig`、`PaginatedSessionsResult`、`SessionInfo`、`SessionStatus`、`SessionDetail`、`DEFAULT_REPLAY_SPEED`、`ReplaySpeed`、`ReplayState`、`SessionTurnsPage`、`SessionTurnsQuery`、`SessionRunResult`、`FileEntry`、`UploadFileEntry`、`UploadFilesOptions`、`AppCliDefinition`、`AppCliAttachment`
696
806
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
807
+ - **沙盒脚本**:`SandboxResource`、`SandboxScriptRequest`、`SandboxScriptResult`。`client.sandbox.execute({ script, cwd, timeout })` 在当前登录用户的沙盒执行脚本,无需创建智能体会话;`cwd` 使用绝对路径,`timeout` 单位为秒,默认及最大值为 120。结果包含合并诊断输出 `output`、独立标准输出 `stdout` 及其截断标志 `stdout_truncated`、可为空的退出码、超时标志和错误信息;无法启动或无法确认停止时应检查 `error`,不能仅凭退出码判断成功。
697
808
  - **远程电脑**:`ComputersResource`、`SessionComputer`、`AccountComputer`
698
809
  - **Headless**:`HeadlessResource`、`RunOptions`、`RunResult`、`RunTrace`
699
- - **消息**:`ChatMessage`、`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`MemoryRefInfo`、`getTextContent`、`getImageParts`、`getFileParts`、`contentPreview`、`groupMessagesByLoop`、`chatErrorForDisplay`、`transformSlashCommand`、`SkillMentionAvailability`、`userFacingErrorText`
810
+ - **消息**:`ChatMessage`、`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`WorkflowStepInfo`(固定流程运行时每步的留档,挂在 `workflow:run` 的 `ToolCallInfo.workflow_steps` 上)、`MemoryRefInfo`、`getTextContent`、`getImageParts`、`getFileParts`、`contentPreview`、`groupMessagesByLoop`、`chatErrorForDisplay`、`transformSlashCommand`、`SkillMentionAvailability`、`userFacingErrorText`
700
811
 
701
812
  会话列表和详情的 `SessionInfo.initiator` 表示首次发起来源:`ui` 为界面,`sdk` 为 SDK;旧会话缺少可靠记录时为 `unknown`,尚未发起会话时为 `null` 或未提供。它不等同于后台运行标记 `is_headless`,后续继续会话不会改变来源。
702
813
 
@@ -711,7 +822,7 @@ chat.renderers = {
711
822
  - `session.send(text, { topicAssetRefs: [{ topic_id, path }] })` 为本条消息选择资产;省略时不继承上一条选择。服务端重新解析元数据,通过 Context 提供拉取指引,不自动读取正文。
712
823
  - `ChatMessage.topic_asset_refs` 是实时与历史一致的展示引用,不能当作访问许可。主题成员不会因此获得其他成员的会话读取权;任务的 `session_id` 只返回给该会话所有者。
713
824
 
714
- 资产引用目前是专用字段,不依赖通用 `context_refs` 注册机制。知识库的 `kbIds` 选择行为保持不变。
825
+ 资产引用目前是专用字段,不依赖通用 `context_refs` 注册机制。
715
826
 
716
827
 
717
828
  主题类型:`ChatGroup`、`ChatGroupRole`、`ChatGroupMember`、`ChatGroupTask`、`CreateChatGroupTask`、`UpdateChatGroupTask`、`ChatGroupAsset`、`ChatGroupAssetList`、`TopicAssetRef`。`normalizeTopicAssetRefs` 仅规范化展示引用并去重,不代替服务端鉴权。
@@ -723,3 +834,178 @@ chat.renderers = {
723
834
  `session.send(text, { refs })` 选择本轮条目;不传 refs 继承上一轮选择,`refs: []` 清除,新的数组替换。AskUser 和暂停续跑沿用本轮冻结内容,下一条新消息才重新读取。流式消息和历史消息都保留 `message.refs`。
724
835
 
725
836
  `ToolEvidence` 是工具调用的原文快照类型。`ToolCallInfo.evidence` 保存引用编号、原文、插件包摘要及具体调用身份;历史查看直接使用该字段,插件停用或卸载后仍可核查当时内容。
837
+
838
+ Solution 的 `skill_tools_enabled`、是否为 `solution-app`、是否采用 `night_build` 安全策略会影响 system 前缀,属于创建时策略;升级时跨值修改返回 422,其他字段也不会部分提交。角色规则和方案技能可正常升级。
839
+
840
+ 非默认模板绑定的会话不能通过 Solution selector 转换为方案会话(422);普通运行配置仍可升级,模板绑定保持原样。
841
+
842
+ 已应用的版本 ID 持久保留:当前版本相同请求幂等返回;被后续升级覆盖的版本返回 409,禁止延迟重试回滚配置。主动回退配置应提交新的版本 ID。
843
+
844
+ 分组会话复用现有 validate_group_session 约束,只允许通用助手;切换到业务 Solution 返回 422,普通运行设置可升级,分组绑定保留。
845
+
846
+
847
+ ### 匿名会话
848
+
849
+ 创建匿名会话需要已认证的客户端。每个匿名会话使用独立持久沙盒,不继承创建者的文件、记忆、个人环境变量或登录凭据。UUID 链接授予该会话的查看、聊天及文件访问权限,请妥善保管。当前不自动回收。
850
+
851
+ ```ts
852
+ const account = new BladeClient({ baseUrl, token: userPat })
853
+ const { session_id } = await account.sessions.createSessionWithRequest({ anonymous: true })
854
+ const link = `${baseUrl}/anonymous/${session_id}`
855
+
856
+ // 访客只有 UUID 即可连接,不需要账号令牌。
857
+ const visitor = account.forAnonymousSession(session_id)
858
+ const session = await visitor.sessions.connect(session_id)
859
+ await session.send("你好")
860
+ // 用完后释放;匿名客户端在最后一个会话引用释放时断开连接。
861
+ session.dispose()
862
+ ```
863
+
864
+ 也可以使用 `new BladeClient({ baseUrl, anonymousSessionId: session_id })`,并把它交给 `BladeProvider` / `ChatView`。`account.sessions.create({ anonymous: true })` 会自动返回已连接的匿名 `AgentSession`;`ChatView createOptions={{ anonymous: true }}` 同样先认证创建,再使用会话凭证连接。账号客户端与匿名客户端分别持有连接,匿名客户端不能创建会话或访问用户级资源。匿名会话只支持 sandbox,不能绑定已有聊天分组、父会话或启用个人记忆。
865
+
866
+ 每个账号最多保留 6 个匿名会话;超过时创建返回 409。使用已认证账号客户端管理和删除旧会话:
867
+
868
+ ```ts
869
+ const retained = await account.sessions.listAnonymousSessions()
870
+ await account.sessions.deleteAnonymousSession(retained[0].id)
871
+ ```
872
+
873
+ 删除会永久移除该会话的沙盒、文件与历史,原链接失效。会话凭证不能调用管理接口。
874
+
875
+ ## 后端应用工具连接
876
+
877
+ `client.connectToolProvider({ providerId, capability, tools })` 返回 `ToolProvider`。仅用于 Node 后端;BA 用户 token 由 BladeClient 提供。`await provider.ready()` 等待已鉴权注册,再将 `provider.url` 和 capability 配入会话 `mcp_sources`。providerId 与 capability 由应用持久化,重连复用。
878
+
879
+ `ToolProviderOptions` 定义注册参数,`ProviderTool` 定义工具与处理函数,`ProviderToolResult` 定义调用结果。
880
+
881
+ 工具包含 `name`、`description`、`inputSchema` 与异步 `handler(arguments, { sessionId, signal })`;结果为 `content`、可选 `structuredContent` 与 `isError`。sessionId 来自 BA 已授权调用,应用还应校验它对应自身业务会话。AbortSignal 表示取消请求,不保证业务回滚。连接恢复不重放调用;业务动作应使用应用自己的幂等 ID。`provider.dispose()` 关闭连接。SDK 退出登录/更换后端也会关闭来源。
882
+
883
+ 连接使用 `/api/app-tools/connect`,HTTP 后端对应 ws,HTTPS 对应 wss;不要求应用暴露回调地址。需要支持此入口的 BA,未加入 SDK/Server 版本校验。
884
+
885
+ ### 可恢复的文本发送
886
+
887
+ 应用需要在回执丢失或进程重启后确认输入时,使用 SDK 准备请求,并在联网前保存:
888
+
889
+ ```ts
890
+ const request = session.prepareSend("制作报告", { mode: "executing" })
891
+ await savePendingRequest(request) // 应用自己的持久化存储
892
+ const receipt = await session.sendPrepared(request)
893
+ // unknown 时恢复保存的 request 再发送,或查询:
894
+ const delivery = await session.getDelivery(request.requestId)
895
+ ```
896
+
897
+ 同一保存请求可重试,BA 不重复入队;同 ID 改变正文或参数会拒绝。接收结果 `accepted` 表示输入已持久化,不表示任务完成。`unknown` 表示无法确认,`not_found` 是查询未找到;不得因此生成新 ID。查询可返回队列项、队列状态和执行 ID;记录保留至会话删除。
898
+
899
+ 此接口复用后端持久化投递:空闲时自动开始,默认在运行中等待,暂停时保留。设置 `queueWhenRunning: false` 时补充当前运行,PA 使用此模式。可靠请求不能编辑,但可以立即补充、取消、重排和继续。支持普通文本、模式、模型、思考与推理强度;AskUser 作答、多模态、whatif 和 replay 继续使用 `send()`。需要支持 `prepared_options` 及 `chat:queue:request` 的 BA;旧服务的普通队列确认不算可靠请求回执。
900
+
901
+ `PreparedSendOptions.headless: true` 让可靠输入使用 BA 的 `submit_result` 交付契约,运行中追加仍遵循 `queueWhenRunning`。`session.getState().lastRun` 保留最近 BA 执行终态(`runId`、`status`、原始 `result`),实时与重连日志补发一致,不依赖消息分页。调用方应通过 `getDelivery(requestId).executionId` 关联本轮执行,不能把上一轮终态当成本轮结果。
902
+
903
+ 公开类型:`PreparedSend` 是可持久化的请求快照,`PreparedSendOptions` 是普通输入的执行参数,`DeliveryReceipt` 是接受或查询结果。
904
+
905
+ 匿名客户端不能读取 owner-only `configuration`,详情不返回 `env`。持链接者可以在共享沙盒执行代码,因此显式提供的环境变量会对该会话参与者可见;不要用它保存创建者的私人凭据。第一版不支持匿名 `app-dev`(软件工厂),也不提供同源 HTML 应用展示;文件可以下载查看。
906
+
907
+ 匿名子客户端保留 `commandTransport`,并为匿名命令额外提供第四个可选参数 `{ anonymousSessionId }`。中间件应使用本次调用的 `send()` 保持匿名凭证,或按该范围使用专属通道;不能把命令转发到另一个账号连接。普通命令仍提供原来的三个参数。
908
+
909
+ ### 消息附加资料
910
+
911
+ 应用可通过 `SendOptions.contextAttachments` 或 `PreparedSendOptions.contextAttachments` 提供消息级资料。`ContextAttachment` 的 `id`、`label`、可读纯文本 `content` 与可选 `data`、`files` 都绑定到该条消息,完整资料通过 Context 提供给模型,正文保持原文。资料不是系统指令。
912
+
913
+ ```ts
914
+ const request = session.prepareSend('任务已完成', {
915
+ mode: 'executing',
916
+ contextAttachments: [{
917
+ id: 'receipt-1', label: '市场周报', content: '状态:已完成\n已验证交付。',
918
+ data: { status: 'succeeded' },
919
+ files: [{ name: '周报.html', session_id: workerSessionId, path: '周报.html' }],
920
+ }],
921
+ })
922
+ // 在网络提交前持久化完整 request,重试时复用同一快照和 requestId。
923
+ await persist(request)
924
+ await session.sendPrepared(request)
925
+ ```
926
+
927
+ 每条消息最多 20 份资料,ID 不重复,每份正文最多 64000 个 Unicode 字符,整个资料 JSON 最多 256 KiB,超限拒绝。文件沿用指定会话的鉴权与路径解析,不接受外部 URL;仅有 `name` 的文件显示不可用。带资料的可靠消息支持 `queueWhenRunning: false` 或「立即补充」;正文和资料一起交给当前运行,资料仍通过标准 Context 注入。原生智能体目前不支持此通道。
928
+
929
+ ## 连接器管理
930
+
931
+ `ConnectorDraft` 描述连接器名称、说明、可选地址和访问密钥。`client.connectors.list()` 读取本人可用的内置与个人连接器,`create({ display_name, description, url, bearer_token? })` 保存已有 Streamable HTTP MCP 服务。`update(name, draft)` 修改个人连接器信息或地址,`delete(name)` 删除个人包并撤销连接配置;内置包不允许编辑或删除。访问密钥经当前身份的请求体发送并由服务端加密保存,不回传明文。保存后通过 `read` / `connect` 的现有配置流程连接;修改包后需要重新连接。
932
+
933
+
934
+ ## 后端应用共享连接
935
+
936
+ PA 等后端服务使用 `BladeApplicationClient`,全部用户共享两条按需建立的 WebSocket。
937
+ 每个用户仍使用自己的 bearer token;REST 请求和会话权限按用户隔离。
938
+
939
+ ```ts
940
+ const application = new BladeApplicationClient({ baseUrl: "http://ba:8020" })
941
+ const client = application.forUser(userId, { token: userToken })
942
+ const session = await client.hub.connect(sessionId)
943
+ // 服务退出时释放全部通道
944
+ application.dispose()
945
+ ```
946
+
947
+ `forUser` 缓存同一用户的客户端;凭据更新调用该客户端的 `setToken`,退出调用 `logoutToken`。
948
+ `connectionCount` 返回当前已建立的物理连接数。重连恢复订阅和工具注册,业务命令与工具动作不重放。
949
+ `ApplicationUserTransport` 是该客户端注入 `BladeClientOptions.applicationTransport` 的传输契约。
950
+ 普通 `BladeClient` 继续使用原有 Socket.IO 和工具连接。
951
+
952
+
953
+ ### 应用提供的文本 Context
954
+
955
+ `PreparedSendOptions.contextAttachments` 中的资料可声明 `update_format: "snapshot" | "diff"`。
956
+ 声明后 `id` 是当前会话内稳定的来源标识,`content` 是完整文本状态(推荐 Markdown),
957
+ runner 将其作为独立 Context 投影:内容不变不再注入,`diff` 由 Projector 对完整状态计算,
958
+ 可见基线丢失时补完整快照。调用者不要传预先计算的 patch。
959
+ 不声明该字段时仍是原有的消息级附件快照。业务处理成功游标由应用自行维护,Context 注入不表示业务已提交。
960
+
961
+ ```ts
962
+ session.send("请处理新增工作", {
963
+ contextAttachments: [
964
+ { id: "work-directory", label: "工作目录", content: directoryMarkdown, update_format: "diff" },
965
+ { id: "pending-work", label: "新增记录", content: pendingMarkdown, update_format: "snapshot" },
966
+ ],
967
+ })
968
+ ```
969
+
970
+ ### 创建或修改连接器
971
+
972
+ `client.connectors.startGeneration({ connector?, onCreated?, initiator? })` 统一创建生成会话并发送带技能引用的可见消息,返回 `{ session_id, sent }`。`connector` 传已有连接器的名称与展示名表示修改;`onCreated` 仅用于宿主登记缓存、导航。`initiator` 默认 `sdk`,内置 Web 使用 `ui`,保留历史列表和通知语义。选定连接器的名称与展示名通过标准 Context 附件传递,用户消息保持固定,包内文字不会被提升成用户指令。SDK 在发送前调用回调,并释放自身的会话引用。生成结束仍需用户到设置里的连接器正式连接。
973
+
974
+ ### Android 浏览器授权回传
975
+
976
+ BA/PA 安卓端在系统浏览器授权时使用 S256 PKCE。服务端接收原生端交回的一次性 code 后,需同时转交该请求的 verifier 和产品;普通网页弹窗不需要这两个字段:
977
+
978
+ ```ts
979
+ await client.auth.exchangeCode({ code, state, clientOrigin, codeVerifier, androidApp: "pa" })
980
+ ```
981
+
982
+ `state` 必须与发起请求时一致;PA 还须校验自身登录请求 cookie。访问令牌保存在自己的服务端,不能通过回调 URL 或网页消息转发。手机端回传及部署约定见仓库 `docs/specs/261026-android-browser-login/`。
983
+
984
+ ### 原生提问和回答
985
+
986
+ `parseAskUserQuestion` 解析 `AskUserQuestion` 的工具参数或结果,返回 `AskUserQuestionData`。每个 `AskUserQuestionItem` 包含问题、`AskUserQuestionOption` 选项和可选多选标记。组件和后端共享这些定义,支持历史中的字符串选项与 `multi_select` 字段。
987
+
988
+ `formatAskUserAnswer` 根据问题及 `AskUserAnswerData` 生成用户可读文本;回答应送回原始会话和工具调用。`getState().askAnswers` 同时从实时事件和历史恢复已回答内容。`sendPrepared` 是普通输入队列,不能代替原生回答。
989
+
990
+ ```ts
991
+ const question = parseAskUserQuestion(toolCall.arguments)
992
+ if (question) {
993
+ const answer = { selections: { 0: [1] }, custom: {} }
994
+ const accepted = await session.send(formatAskUserAnswer(question, answer), {
995
+ askUserAnswer: { tool_call_id: toolCall.id, ...answer },
996
+ })
997
+ // 只有 accepted 为 true,才能把此次投递视为服务端已接收。
998
+ }
999
+ ```
1000
+
1001
+ ### 桌面系统浏览器回传
1002
+
1003
+ Electron 主进程负责临时监听 `http://127.0.0.1:<port>/auth/callback`,并用
1004
+ `desktop_app=ba|pa`、`callback_uri`、`code_challenge`(S256)、`state` 和
1005
+ `client_origin` 请求 `/api/auth/sdk/authorize`。平台和回调绑定不可省略或改写。
1006
+ 服务端通过 `auth.exchangeCode({ code, state, clientOrigin, codeVerifier,
1007
+ desktopApp: "pa", callbackUri })` 换码;PA 的 PAT 留在服务端。
1008
+ BA 桌面使用同源 `/api/auth/sdk/desktop-session` 建立自身 cookie 会话。
1009
+ `auth.login()` 仍是普通浏览器的弹窗登录,不负责本机监听。
1010
+
1011
+ 飞书初始授权的 `permission_template` 标明官方推荐清单来源;`permissions` 可包含尚无 SW 操作的推荐权限。`ConnectorPermission.template/category_name` 用于展示清单,`app_status` 与 `user_status` 分别表示应用开通和本人同意;未知不等于拒绝,也不能据此断言企业免审。授权范围不等于工具实现范围。
@@ -0,0 +1,19 @@
1
+ import { BladeClient, type BladeClientOptions } from "./blade-client";
2
+ import type { TypedSocket } from "./socket";
3
+ /** Transport selected by BladeApplicationClient; ordinary clients use Socket.IO. */
4
+ export interface ApplicationUserTransport {
5
+ socket(): TypedSocket;
6
+ provider(): WebSocket;
7
+ invalidate(): void;
8
+ }
9
+ /** Backend application: all users share two lazy WebSockets; REST keeps user credentials. */
10
+ export declare class BladeApplicationClient {
11
+ private readonly options;
12
+ private readonly channels;
13
+ private readonly clients;
14
+ private readonly applicationId;
15
+ constructor(options: Pick<BladeClientOptions, "baseUrl" | "fetchImpl">);
16
+ forUser(userId: string, options: Omit<BladeClientOptions, "baseUrl" | "applicationTransport" | "anonymousSessionId">): BladeClient;
17
+ get connectionCount(): number;
18
+ dispose(): void;
19
+ }
@@ -1,11 +1,16 @@
1
+ import type { ApplicationUserTransport } from "./application-client";
2
+ import { SandboxResource } from "./resources/sandbox";
3
+ import { ToolProvider, type ToolProviderOptions } from "./tool-provider";
1
4
  import { type LoginOptions, type LoginResult, type TokenStorageMode } from "./auth-login";
2
5
  import { type BladeFetchInit, type HttpMethod } from "./rest";
3
6
  import { AuthResource } from "./resources/auth";
4
7
  import { HeadlessResource } from "./resources/headless";
5
8
  import { ComputersResource } from "./resources/computers";
9
+ import { PhonesResource } from "./resources/phones";
6
10
  import { ModelsResource } from "./resources/models";
7
11
  import { SessionsResource } from "./resources/sessions";
8
12
  import { PluginsResource } from "./resources/plugins";
13
+ import { ConnectorsResource } from "./resources/connectors";
9
14
  import { ChatGroupsResource } from "./resources/chat-groups";
10
15
  import { SiteVisitsResource } from "./resources/site-visits";
11
16
  import { SessionHub } from "./session/hub";
@@ -18,6 +23,8 @@ export interface BladeClientOptions {
18
23
  * 浏览器场景也可以不传,改用 client.auth.login() 弹窗登录。
19
24
  */
20
25
  token?: string | (() => string | null | undefined);
26
+ /** 只访问此匿名会话;UUID 链接本身是访问凭证,不使用账号令牌。 */
27
+ anonymousSessionId?: string;
21
28
  /**
22
29
  * Socket.IO 专用令牌。第一方应用可在 REST 继续使用 cookie 刷新的同时,
23
30
  * 把 cookie 换得的短期令牌只用于实时连接;不传时复用 token。
@@ -26,24 +33,34 @@ export interface BladeClientOptions {
26
33
  /** login() 取得的令牌存放位置,默认 "local"(localStorage,刷新页面不掉登录)。 */
27
34
  tokenStorage?: TokenStorageMode;
28
35
  /** Host-owned command transport; event subscriptions stay on the normal socket. */
29
- commandTransport?: (event: string, payload: Record<string, unknown>, send: () => Promise<Record<string, unknown>>) => Promise<Record<string, unknown>>;
36
+ commandTransport?: (event: string, payload: Record<string, unknown>, send: () => Promise<Record<string, unknown>>, scope?: {
37
+ anonymousSessionId: string;
38
+ }) => Promise<Record<string, unknown>>;
39
+ /** Backend shared application transport, installed by BladeApplicationClient. */
40
+ applicationTransport?: ApplicationUserTransport;
30
41
  fetchImpl?: typeof fetch;
31
42
  onRefreshSuccess?: () => void | Promise<void>;
32
43
  }
33
44
  export declare class BladeClient {
45
+ private readonly anonymousClients;
46
+ private readonly toolProviders;
34
47
  private refreshPromise;
48
+ private cookieLogoutPending;
35
49
  private socketInstance;
36
50
  private readonly socketReplacementListeners;
37
- /** login() / setToken() 热挂载的令牌,优先级高于构造参数。 */
38
51
  private runtimeToken;
39
52
  readonly options: BladeClientOptions;
53
+ readonly sandbox: SandboxResource;
40
54
  readonly auth: AuthResource;
41
55
  readonly headless: HeadlessResource;
42
56
  readonly computers: ComputersResource;
57
+ /** 用户接进来的手机:账号级清单、会话启用、画面信令。 */
58
+ readonly phones: PhonesResource;
43
59
  readonly models: ModelsResource;
44
60
  readonly sessions: SessionsResource;
45
61
  /** 按用户读取的插件目录与图标;没有会话时也能用。 */
46
62
  readonly plugins: PluginsResource;
63
+ readonly connectors: ConnectorsResource;
47
64
  readonly chatGroups: ChatGroupsResource;
48
65
  /**
49
66
  * 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
@@ -53,6 +70,8 @@ export declare class BladeClient {
53
70
  /** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
54
71
  readonly hub: SessionHub;
55
72
  constructor(options: BladeClientOptions);
73
+ /** Backend application tools, callable over an outbound WebSocket. */
74
+ connectToolProvider(options: ToolProviderOptions): ToolProvider;
56
75
  /**
57
76
  * 弹窗授权登录:用户在弹出的授权页点击"许可授权"后自动取得 PAT,
58
77
  * REST 与 Socket.IO 立即生效。只在浏览器可用。
@@ -79,6 +98,8 @@ export declare class BladeClient {
79
98
  * 安全约束:该令牌只应发往 baseUrl 同源接口,不要附加到第三方域名的请求上。
80
99
  */
81
100
  get token(): string | null;
101
+ /** 获取只访问指定匿名会话的客户端;创建仍由已认证的原客户端完成。 */
102
+ forAnonymousSession(sessionId: string): BladeClient;
82
103
  setBaseUrl(baseUrl: string): void;
83
104
  /** 底层 Socket.IO 实例因后端地址变化而被替换时通知高级集成层。 */
84
105
  onSocketReplaced(listener: () => void): () => void;
@@ -102,13 +123,6 @@ export declare class BladeClient {
102
123
  private isSameBackendUrl;
103
124
  private shouldRefreshFor401;
104
125
  private hasExplicitBearerToken;
105
- /**
106
- * 等一次令牌刷新,但调用方中止请求时不再等下去。
107
- *
108
- * 刷新本身是共享的(`refreshPromise`,别的请求也在等它),这里只是让这次已被取消的
109
- * 请求尽早结束——否则它会一直挂在调用方(定位)的串行队列里,用户改选的新目标要等到
110
- * 刷新结束才轮得到。
111
- */
112
126
  private tryRefreshUnlessAborted;
113
127
  private tryRefresh;
114
128
  private resolveTokenForUrl;