@blade-hq/agent-client 2612.0.0-beta.44 → 2612.0.0-beta.46

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/README.md CHANGED
@@ -452,6 +452,47 @@ await client.computers.setEnabled(sessionId, computer.id, false) // 停用
452
452
 
453
453
  `ComputersResource` 是 `client.computers` 的类型。
454
454
 
455
+ ### 手机:client.phones
456
+
457
+ 用户接进来的 Android 手机。手机在这台工作环境里就是一台 adb 设备,智能体可以直接
458
+ 用 `adb` 命令操作它。和一键可用的电脑不同,手机要先在设置页拿到一把**接入密钥**
459
+ 填进手机 App,App 连上来之后设备自动登记。
460
+
461
+ ```ts
462
+ const { phones } = await client.phones.listAccount()
463
+ // PhoneList → { phones: PhoneDevice[] }
464
+ // PhoneDevice: { id, label, serial, model, android_version, created_at,
465
+ // last_seen_at, online, adb_online, enabled, session_id }
466
+
467
+ await client.phones.setEnabled(sessionId, phone.id, true) // 本次会话可以用它
468
+ await client.phones.setEnabled(sessionId, phone.id, false) // 停用
469
+ ```
470
+
471
+ `online` 和 `adb_online` 是**两件独立的事**,会单独坏:
472
+
473
+ - `online`:手机此刻连着后端。连上就算,不等沙盒。
474
+ - `adb_online`:沙盒里的 adb 此刻能对它执行命令。需要 adb 认证通过。
475
+
476
+ 手机连着但沙盒里没装 adb、或者认证没过,`online` 仍然是 true 而 `adb_online` 是
477
+ false。合成一个「离线」会让人去排查网络,而问题在沙盒。
478
+
479
+ `PhoneState` 是把这两个字段折成 UI 状态的唯一出处(`phoneState(phone)`),不要
480
+ 在调用方重新拼一遍判断。
481
+
482
+ ```ts
483
+ const key = await client.phones.getKey()
484
+ // PhoneAccessKey → { key, hub_url, connector_url }
485
+
486
+ await client.phones.resetKey() // 换一把新密钥并作废旧密钥
487
+ await client.phones.revokeKey() // 作废,之后手机重连一律被拒
488
+ ```
489
+
490
+ `key` 是以 `pk_` 开头的长期凭据,没作废就一直有效。`hub_url` 是服务器地址(协议+
491
+ 主机+端口),`connector_url` 是手机 App 的下载地址,管理员可以配成自己的静态文件。
492
+
493
+ `PhonesResource` 是 `client.phones` 的类型,`PhoneDevice` / `PhoneAccessKey` /
494
+ `PhoneList` / `PhoneHealth` / `PhonePipeline` / `PhoneState` 是配套的公开类型。
495
+
455
496
  ## AgentSession
456
497
 
457
498
  一个会话的实时状态机。**状态归属实例**:同一页面建多个会话互不干扰。
@@ -751,7 +792,7 @@ chat.renderers = {
751
792
  - **页面协作**:`CommandHandler`、`BladeChatMountRenderers`、`MountRenderer`、`MountRendererObject`、`MountResult`、`MountHandle`
752
793
  - **会话资源(REST)**:`SessionsResource`、`CreateSessionRequest`、`McpSourceConfig`、`PaginatedSessionsResult`、`SessionInfo`、`SessionStatus`、`SessionDetail`、`DEFAULT_REPLAY_SPEED`、`ReplaySpeed`、`ReplayState`、`SessionTurnsPage`、`SessionTurnsQuery`、`FileEntry`、`UploadFileEntry`、`UploadFilesOptions`、`AppCliDefinition`、`AppCliAttachment`
753
794
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
754
- - **沙盒脚本**:`SandboxResource`、`SandboxScriptRequest`、`SandboxScriptResult`。`client.sandbox.execute({ script, cwd, timeout })` 在当前登录用户的沙盒执行脚本,无需创建智能体会话;`cwd` 使用绝对路径,`timeout` 单位为秒,默认及最大值为 120。结果包含有界输出、可为空的退出码、超时标志和错误信息;无法启动或无法确认停止时应检查 `error`,不能仅凭退出码判断成功。
795
+ - **沙盒脚本**:`SandboxResource`、`SandboxScriptRequest`、`SandboxScriptResult`。`client.sandbox.execute({ script, cwd, timeout })` 在当前登录用户的沙盒执行脚本,无需创建智能体会话;`cwd` 使用绝对路径,`timeout` 单位为秒,默认及最大值为 120。结果包含合并诊断输出 `output`、独立标准输出 `stdout` 及其截断标志 `stdout_truncated`、可为空的退出码、超时标志和错误信息;无法启动或无法确认停止时应检查 `error`,不能仅凭退出码判断成功。
755
796
  - **远程电脑**:`ComputersResource`、`SessionComputer`、`AccountComputer`
756
797
  - **Headless**:`HeadlessResource`、`RunOptions`、`RunResult`、`RunTrace`
757
798
  - **消息**:`ChatMessage`、`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`WorkflowStepInfo`(固定流程运行时每步的留档,挂在 `workflow:run` 的 `ToolCallInfo.workflow_steps` 上)、`MemoryRefInfo`、`getTextContent`、`getImageParts`、`getFileParts`、`contentPreview`、`groupMessagesByLoop`、`chatErrorForDisplay`、`transformSlashCommand`、`SkillMentionAvailability`、`userFacingErrorText`
@@ -845,6 +886,8 @@ const delivery = await session.getDelivery(request.requestId)
845
886
 
846
887
  此接口复用后端持久化投递:空闲时自动开始,默认在运行中等待,暂停时保留。设置 `queueWhenRunning: false` 时补充当前运行,PA 使用此模式。可靠请求不能编辑,但可以立即补充、取消、重排和继续。支持普通文本、模式、模型、思考与推理强度;AskUser 作答、多模态、whatif 和 replay 继续使用 `send()`。需要支持 `prepared_options` 及 `chat:queue:request` 的 BA;旧服务的普通队列确认不算可靠请求回执。
847
888
 
889
+ `PreparedSendOptions.headless: true` 让可靠输入使用 BA 的 `submit_result` 交付契约,运行中追加仍遵循 `queueWhenRunning`。`session.getState().lastRun` 保留最近 BA 执行终态(`runId`、`status`、原始 `result`),实时与重连日志补发一致,不依赖消息分页。调用方应通过 `getDelivery(requestId).executionId` 关联本轮执行,不能把上一轮终态当成本轮结果。
890
+
848
891
  公开类型:`PreparedSend` 是可持久化的请求快照,`PreparedSendOptions` 是普通输入的执行参数,`DeliveryReceipt` 是接受或查询结果。
849
892
 
850
893
  匿名客户端不能读取 owner-only `configuration`,详情不返回 `env`。持链接者可以在共享沙盒执行代码,因此显式提供的环境变量会对该会话参与者可见;不要用它保存创建者的私人凭据。第一版不支持匿名 `app-dev`(软件工厂),也不提供同源 HTML 应用展示;文件可以下载查看。
@@ -915,3 +958,13 @@ session.send("请处理新增工作", {
915
958
  ### 创建或修改连接器
916
959
 
917
960
  `client.connectors.startGeneration({ connector?, onCreated?, initiator? })` 统一创建生成会话并发送带技能引用的可见消息,返回 `{ session_id, sent }`。`connector` 传已有连接器的名称与展示名表示修改;`onCreated` 仅用于宿主登记缓存、导航。`initiator` 默认 `sdk`,内置 Web 使用 `ui`,保留历史列表和通知语义。选定连接器的名称与展示名通过标准 Context 附件传递,用户消息保持固定,包内文字不会被提升成用户指令。SDK 在发送前调用回调,并释放自身的会话引用。生成结束仍需用户到设置里的连接器正式连接。
961
+
962
+ ### Android 浏览器授权回传
963
+
964
+ BA/PA 安卓端在系统浏览器授权时使用 S256 PKCE。服务端接收原生端交回的一次性 code 后,需同时转交该请求的 verifier 和产品;普通网页弹窗不需要这两个字段:
965
+
966
+ ```ts
967
+ await client.auth.exchangeCode({ code, state, clientOrigin, codeVerifier, androidApp: "pa" })
968
+ ```
969
+
970
+ `state` 必须与发起请求时一致;PA 还须校验自身登录请求 cookie。访问令牌保存在自己的服务端,不能通过回调 URL 或网页消息转发。手机端回传及部署约定见仓库 `docs/specs/261026-android-browser-login/`。
@@ -6,6 +6,7 @@ import { type BladeFetchInit, type HttpMethod } from "./rest";
6
6
  import { AuthResource } from "./resources/auth";
7
7
  import { HeadlessResource } from "./resources/headless";
8
8
  import { ComputersResource } from "./resources/computers";
9
+ import { PhonesResource } from "./resources/phones";
9
10
  import { ModelsResource } from "./resources/models";
10
11
  import { SessionsResource } from "./resources/sessions";
11
12
  import { PluginsResource } from "./resources/plugins";
@@ -53,6 +54,8 @@ export declare class BladeClient {
53
54
  readonly auth: AuthResource;
54
55
  readonly headless: HeadlessResource;
55
56
  readonly computers: ComputersResource;
57
+ /** 用户接进来的手机:账号级清单、会话启用、画面信令。 */
58
+ readonly phones: PhonesResource;
56
59
  readonly models: ModelsResource;
57
60
  readonly sessions: SessionsResource;
58
61
  /** 按用户读取的插件目录与图标;没有会话时也能用。 */
@@ -224,7 +224,7 @@ var ToolProvider = class {
224
224
 
225
225
  // src/version.ts
226
226
  var SDK_NAME = "agent-client";
227
- var SDK_VERSION = true ? "2612.0.0-beta.44" : "1.1.1";
227
+ var SDK_VERSION = true ? "2612.0.0-beta.46" : "1.1.1";
228
228
 
229
229
  // src/auth-login.ts
230
230
  function isAuthCallbackMessage(value) {
@@ -378,6 +378,7 @@ var AuthResource = class {
378
378
  headers: { Origin: params.clientOrigin },
379
379
  body: JSON.stringify({
380
380
  code: params.code,
381
+ ...params.codeVerifier ? { code_verifier: params.codeVerifier, android_app: params.androidApp } : {},
381
382
  state: params.state,
382
383
  client_origin: params.clientOrigin
383
384
  })
@@ -3259,6 +3260,94 @@ var ComputersResource = class {
3259
3260
  }
3260
3261
  };
3261
3262
 
3263
+ // src/resources/phones.ts
3264
+ function phoneState(phone) {
3265
+ if (phone.enabled) return "enabled";
3266
+ return phone.online ? "idle" : "offline";
3267
+ }
3268
+ function sortPhones(phones) {
3269
+ return [...phones].sort((a, b) => {
3270
+ const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
3271
+ if (byCreated !== 0) return byCreated;
3272
+ return a.label.localeCompare(b.label);
3273
+ });
3274
+ }
3275
+ function phoneSerialLabel(phone) {
3276
+ return phone.serial.trim();
3277
+ }
3278
+ var PhonesResource = class {
3279
+ constructor(client) {
3280
+ this.client = client;
3281
+ }
3282
+ client;
3283
+ /** 账号下的全部手机(不含"本次会话是否可用")。 */
3284
+ async listAccount() {
3285
+ return this.client.json("GET", "/api/phone/devices");
3286
+ }
3287
+ /**
3288
+ * 当前接入密钥;没有就补一个。
3289
+ *
3290
+ * **取的时候不轮换**:每次打开设置页都换钥匙会让已配好的手机集体掉线。
3291
+ */
3292
+ getKey() {
3293
+ return this.client.json("GET", "/api/phone/key");
3294
+ }
3295
+ /** 换一把新密钥(旧的立即失效)。 */
3296
+ resetKey() {
3297
+ return this.client.json("POST", "/api/phone/key/reset");
3298
+ }
3299
+ /** 作废密钥;设备记录保留,用户下次取密钥会得到一把新的。 */
3300
+ async revokeKey() {
3301
+ await this.client.fetch("DELETE", "/api/phone/key");
3302
+ }
3303
+ async remove(phoneId) {
3304
+ await this.client.fetch("DELETE", `/api/phone/devices/${encodeURIComponent(phoneId)}`);
3305
+ }
3306
+ /** 本次会话能用的手机 + 账号下的其他手机。 */
3307
+ listForSession(sessionId) {
3308
+ return this.client.json(
3309
+ "GET",
3310
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones`
3311
+ );
3312
+ }
3313
+ setEnabled(sessionId, phoneId, enabled) {
3314
+ return this.client.json(
3315
+ "PUT",
3316
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones/${encodeURIComponent(phoneId)}/enabled`,
3317
+ { enabled }
3318
+ );
3319
+ }
3320
+ health(sessionId, phoneId) {
3321
+ return this.client.json(
3322
+ "GET",
3323
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones/${encodeURIComponent(phoneId)}/health`
3324
+ );
3325
+ }
3326
+ /**
3327
+ * WHEP 信令:POST 一个 SDP offer,拿到 answer。
3328
+ *
3329
+ * 走 `textFromInit` 而不是 `json()`:SDP 是纯文本,`json()` 会强制序列化成
3330
+ * JSON;也不能用裸 `fetch`,那会丢掉 bearer token。
3331
+ */
3332
+ async whep(sessionId, phoneId, offerSdp) {
3333
+ return this.client.textFromInit(
3334
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones/${encodeURIComponent(phoneId)}/whep`,
3335
+ {
3336
+ method: "POST",
3337
+ body: offerSdp,
3338
+ headers: { "content-type": "application/sdp" }
3339
+ }
3340
+ );
3341
+ }
3342
+ /** 用户点「刷新画面」:让后台重新确认隧道服务并忘掉记住的 token。 */
3343
+ async wake(sessionId, phoneId) {
3344
+ await this.client.json(
3345
+ "POST",
3346
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones/${encodeURIComponent(phoneId)}/wake`
3347
+ );
3348
+ }
3349
+ };
3350
+
3262
3351
  // src/resources/models.ts
3263
3352
  var ModelsResource = class {
3264
3353
  constructor(client) {
@@ -5021,6 +5110,7 @@ function createInitialSessionState(sessionId) {
5021
5110
  mode: null,
5022
5111
  connection: "disconnected",
5023
5112
  errorMessage: null,
5113
+ lastRun: null,
5024
5114
  askAnswers: {},
5025
5115
  agentLoops: {},
5026
5116
  activeCompaction: null,
@@ -6027,7 +6117,7 @@ var AgentSession = class _AgentSession {
6027
6117
  /** Prepare a durable text input without sending any network request. */
6028
6118
  prepareSend(content, options = {}) {
6029
6119
  if (!content.trim()) throw new Error("\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A");
6030
- if (Object.keys(options).some((key) => !["mode", "model", "thinkingOverride", "reasoningLevel", "contextAttachments", "queueWhenRunning"].includes(key))) {
6120
+ if (Object.keys(options).some((key) => !["mode", "model", "thinkingOverride", "reasoningLevel", "contextAttachments", "queueWhenRunning", "headless"].includes(key))) {
6031
6121
  throw new Error("\u53EF\u9760\u6587\u672C\u53D1\u9001\u4E0D\u652F\u6301\u8FD9\u4E9B\u53C2\u6570\uFF0C\u8BF7\u4F7F\u7528 send()");
6032
6122
  }
6033
6123
  const snapshot2 = JSON.parse(JSON.stringify(options));
@@ -6046,6 +6136,7 @@ var AgentSession = class _AgentSession {
6046
6136
  prepared_options: {
6047
6137
  mode: request.options.mode,
6048
6138
  model: request.options.model,
6139
+ ...request.options.headless !== void 0 ? { headless: request.options.headless } : {},
6049
6140
  ...request.options.queueWhenRunning !== void 0 ? { queue_when_running: request.options.queueWhenRunning } : {},
6050
6141
  thinking_override: request.options.thinkingOverride,
6051
6142
  reasoning_level: request.options.reasoningLevel,
@@ -6673,11 +6764,13 @@ var AgentSession = class _AgentSession {
6673
6764
  if (delivery.reload) {
6674
6765
  this.activeReplayPlaybackId = null;
6675
6766
  this.projectionBuilder.reset();
6767
+ this.update((state) => ({ ...state, lastRun: null }));
6676
6768
  this.pendingTurnPatches.clear();
6677
6769
  this.epochReloadRequested += 1;
6678
6770
  }
6679
6771
  this.runEpochReload();
6680
6772
  for (const event of events) {
6773
+ if (event.type === "chat:end") this.recordRunResult(event.payload);
6681
6774
  const playbackId = typeof event.payload.playback_id === "string" ? event.payload.playback_id : null;
6682
6775
  if (event.type === "chat:start") this.activeReplayPlaybackId = null;
6683
6776
  if (event.type === "replay:start" && playbackId) {
@@ -6777,6 +6870,14 @@ var AgentSession = class _AgentSession {
6777
6870
  isStreaming: true
6778
6871
  }));
6779
6872
  }
6873
+ recordRunResult(payload) {
6874
+ const runId = payload.chat_run_id;
6875
+ if (payload.queued || typeof runId !== "string" || !runId) return;
6876
+ this.update((state) => ({
6877
+ ...state,
6878
+ lastRun: { runId, status: payload.status ?? "completed", result: payload.result }
6879
+ }));
6880
+ }
6780
6881
  _handleChatEnd(payload = {}) {
6781
6882
  const status = payload.status ?? "completed";
6782
6883
  if (payload.queued === true) {
@@ -6786,6 +6887,7 @@ var AgentSession = class _AgentSession {
6786
6887
  return false;
6787
6888
  }
6788
6889
  }
6890
+ this.recordRunResult(payload);
6789
6891
  this.pendingChatRequestId = null;
6790
6892
  this.noteLiveStatus();
6791
6893
  this.flushPatches();
@@ -8009,6 +8111,8 @@ var BladeClient = class _BladeClient {
8009
8111
  auth;
8010
8112
  headless;
8011
8113
  computers;
8114
+ /** 用户接进来的手机:账号级清单、会话启用、画面信令。 */
8115
+ phones;
8012
8116
  models;
8013
8117
  sessions;
8014
8118
  /** 按用户读取的插件目录与图标;没有会话时也能用。 */
@@ -8034,6 +8138,7 @@ var BladeClient = class _BladeClient {
8034
8138
  this.auth = new AuthResource(this);
8035
8139
  this.headless = new HeadlessResource(this);
8036
8140
  this.computers = new ComputersResource(this);
8141
+ this.phones = new PhonesResource(this);
8037
8142
  this.models = new ModelsResource(this);
8038
8143
  this.sessions = new SessionsResource(this);
8039
8144
  this.plugins = new PluginsResource(this);
@@ -9073,6 +9178,9 @@ export {
9073
9178
  computerDaemonVersion,
9074
9179
  sortComputers,
9075
9180
  ComputersResource,
9181
+ phoneState,
9182
+ sortPhones,
9183
+ phoneSerialLabel,
9076
9184
  ModelsResource,
9077
9185
  isLocalConnectorName,
9078
9186
  ConnectorsResource,
@@ -9132,4 +9240,4 @@ export {
9132
9240
  BladeClient,
9133
9241
  BladeApplicationClient
9134
9242
  };
9135
- //# sourceMappingURL=chunk-WSXAKVX6.js.map
9243
+ //# sourceMappingURL=chunk-KX57J5KV.js.map