@blade-hq/agent-client 2610.0.0-beta.51 → 2610.0.0-beta.53

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
@@ -397,6 +397,7 @@ chat.getState()
397
397
  // connection, 连接状态:"connected" | "connecting" | "reconnecting" | "disconnected"
398
398
  // errorMessage, 最近一次运行错误
399
399
  // replay, 回放状态:{ isReplay, speed, sourceSessionId } | null
400
+ // queue, 等待消息队列快照:{ session_id, revision, paused, pause_reason, items }
400
401
  // viewerRole, "owner" | "viewer" | null(只读身份改不动会话)
401
402
  // nextBefore, loadingOlder, 历史分页游标与加载状态
402
403
  // turns, askAnswers, agentLoops, activeCompaction 进阶字段
@@ -412,8 +413,8 @@ const unsubscribe = chat.subscribe(() => rerender(chat.getState()))
412
413
  ```ts
413
414
  const accepted = await chat.send("你好") // 服务端接受后为 true;拒绝时为 false
414
415
  await chat.send("换个方案", { mode: "planning" }) // 指定模式/模型等选项
415
- chat.append("补充:预算不超过 5 万") // 智能体运行中追加说明
416
- await chat.stop() // 停止当前回复
416
+ await chat.queueMessage("补充:预算不超过 5 万") // 运行中入队;返回服务端确认结果与冲突原因
417
+ await chat.stop() // 停止当前回复(同时暂停队列,需 resumeQueue 继续)
417
418
  await chat.compact() // 手动压缩上下文
418
419
  if (chat.hasOlderHistory) await chat.loadOlderHistory()
419
420
  chat.dispose() // 彻底释放(什么时候该调见下方说明)
@@ -423,6 +424,28 @@ chat.dispose() // 彻底释放(什么时候
423
424
  > 1. 用户明确关闭或删除了这个会话,之后不会再回来;
424
425
  > 2. 页面长期开着并且会不断创建一次性会话(比如批量任务面板),需要主动回收。
425
426
 
427
+ ### 等待消息队列
428
+
429
+ 队列由服务端持久化:运行中发送、暂停期间发送都进入队尾,按顺序逐条执行;关掉页面不影响已确认的入队,重新打开恢复真实状态。等待消息**不会**提前伪装成聊天记录——它只在队列区域展示,真正被接收后才进入消息流。
430
+
431
+ ```ts
432
+ const result = await chat.queueMessage("顺便看下这个报错") // 入队(运行中排队 / 暂停期间照常入队)
433
+ // result: { ok, conflict, snapshot, message }
434
+
435
+ await chat.updateQueuedMessage(item.id, item.version, "改好的正文")
436
+ await chat.reorderQueuedMessages(queue.revision, pendingIdsInNewOrder) // ordered_ids 必须是当前 pending 条目 id 的完整集合
437
+ await chat.cancelQueuedMessage(item.id, item.version)
438
+ await chat.deliverQueuedMessage(item.id, item.version) // 立即补充:交给当前运行在下一个接收点消费
439
+ await chat.resumeQueue() // 暂停后继续执行
440
+ await chat.refreshQueue() // 手动拉一份权威快照(首次连接与每次重连已自动拉取)
441
+ ```
442
+
443
+ `chat.getState().queue` 是 `SessionQueueSnapshot`:`{ session_id, revision, paused, pause_reason, items }`。它只会被 revision 更新的快照替换——乱序到达的旧广播不会把新状态顶回去。`items` 只包含 `pending`(待执行)与 `delivering`(等待接收)。
444
+
445
+ - **冲突处理**:`conflict` 表示乐观并发或状态校验失败,服务端快照已自动刷新到本地,此时保留用户草稿并说明原因即可;`ok=false` 且 `snapshot` 为 null(例如鉴权失败)时不要改动本地队列。`queueMessage` 结果未知(ack 超时)时会用同一个 `client_request_id` 重试一次,不会制造第二条。
446
+ - **可操作性判定**:`canEditQueuedMessage` / `canCancelQueuedMessage` / `canDeliverQueuedMessage` 只对 `pending` 返回 true;文案用 `queuedMessageStatusLabel` 与 `queuePauseReasonLabel`。
447
+ - **自己处理广播**:需要绕过状态机时用 `isSessionQueueSnapshot` + `shouldApplySnapshot` + `EMPTY_SESSION_QUEUE`;解析自定义 ack 用 `parseQueueAck`,返回统一的 `QueueOperationResult`(`QueueAck` 是 ack 的线格式)。
448
+
426
449
  ### 页面协作:让智能体和你的页面互动
427
450
 
428
451
  **页面状态与业务后端 → 智能体**:
@@ -684,9 +707,11 @@ HTML 一律判无效);`isInternalStatusToolUiCard` 识别内部「阶段进
684
707
  - **声明式会话**:`SessionDefinition`、`SolutionDefinition`、`SkillDefinition`、`SessionConfig`、`TextFile`、`SessionSetupError`、`SessionSetupStage`
685
708
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
686
709
  - **聊天项目资源(REST)**:`ChatProjectsResource`、`ChatProject`(含 `delete` 永久删除)
710
+ - **全站访问统计(REST)**:`SiteVisitsResource`、`SiteVisitsSummary`(`client.siteVisits.record()` 上报一次页面访问、`client.siteVisits.summary()` 只读快照)。这两个操作走独立匿名传输:不带 bearer、不触发 401 登录刷新,只依赖匿名访客 Cookie,供内置 Web 同源使用。
687
711
  - **会话资源(REST)**:`SessionsResource`、`CreateSessionRequest`、`ImportSessionOptions`、`AppCliDefinition`、`AppCliAttachment`、`AttachAppOptions`、`PaginatedSessionsResult`、`GlobalSearchResult`、`GlobalSearchResultItem`、`GlobalSearchConversationResult`、`GlobalSearchFileResult`、`SessionHistory`、`SessionContextStats`、`ResultFeedback`、`ResultFeedbackReason`、`ShareLinkResult`、`FileEntry`、`UploadFileEntry`、`UploadFilesOptions`、`SessionProfile`、`SessionDetail`、`SessionInfo`、`SessionStatus`、`SessionPortMapping`、`ModeId`、`TemplateId`、`PrimarySkillSnapshot`、`PrimarySkillParallelMode`
688
712
  - **会话回放**:`ReplayState`、`ReplaySpeed`、`ReplayPreview`、`ReplaySnapshot`、`toReplaySnapshot`、`DEFAULT_REPLAY_SPEED`
689
713
  - **会话状态机**:`SessionHub`、`SessionConnectOptions`、`SessionState`、`SendOptions`、`ConnectionStatus`、`AskUserAnswerData`、`AgentLoopInfo`、`ActiveCompactionState`、`createInitialSessionState`、`AgentSessionEventName`
714
+ - **会话消息队列**:`SessionQueueSnapshot`、`QueuedMessage`、`QueuedMessageStatus`、`QueueAck`、`QueueOperationResult`、`EMPTY_SESSION_QUEUE`、`isSessionQueueSnapshot`、`shouldApplySnapshot`、`parseQueueAck`、`canEditQueuedMessage`、`canCancelQueuedMessage`、`canDeliverQueuedMessage`、`queuedMessageStatusLabel`、`queuePauseReasonLabel`
690
715
  - **页面协作**:`EmbeddedChat`、`EmbeddedChatOptions`、`CommandHandler`、`CommandEnvelope`、`InboundAction`、`InboundEnvelope`、`isCommandEnvelope`、`isInboundEnvelope`
691
716
  - **消息与投影协议**:`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`ToolBridgeContent`、`CompactionInfo`、`ContextProjectionData`、`ContextProjectionFields`、`ContextDisplayState`、`ContextGroupDisplayState`、`ContextAction`、`ContextSourceInfo`、`MemoryRefInfo`、`ArchivedFileInfo`、`ArchivedToolCallInfo`、`TurnProjection`、`ContentBlock`、`PatchEnvelope`、`MemoryRef`、`PostChatFollowup`、`FinalArtifact`、`contextProjectionData`、`getContextDisplayState`、`getContextGroupDisplayState`、`groupAdjacentContextRuns`、`latestPostChatFollowup`、`buildMessageContent`、`normalizeMessageContent`、`isHiddenInternalMessage`、`transformSlashCommand`、`SkillMentionAvailability`、`extractTextAttachments`、`ParsedTextAttachment`、`ParsedTextContext`
692
717
  - **MCP Apps 留档与卡片判定**:`McpAppData`、`isMcpAppData`、`McpAppContextState`、`McpAppContextUpdate`、`ToolUiCard`、`ToolUiCardContentType`、`InlineToolUiCardEntry`、`PreviewToolUiCardEntry`、`isToolUiCard`、`resolveToolUiCardContent`、`buildToolUiCardKey`、`isInternalStatusToolUiCard`、`isAppDevToolUiCard`、`collectInlineToolUiCards`、`collectPreviewToolUiCards`(`McpAppContextState` / `McpAppContextUpdate` 配合 `sessions.getMcpAppContext` / `sessions.updateMcpAppContext`:App 保存的状态在下一个新运行才进入模型上下文)
@@ -6,6 +6,7 @@ import { ComputersResource } from "./resources/computers";
6
6
  import { ModelsResource } from "./resources/models";
7
7
  import { SessionsResource } from "./resources/sessions";
8
8
  import { ChatProjectsResource } from "./resources/chat-projects";
9
+ import { SiteVisitsResource } from "./resources/site-visits";
9
10
  import { SessionHub } from "./session/hub";
10
11
  import { type TypedSocket } from "./socket";
11
12
  export interface BladeClientOptions {
@@ -39,6 +40,11 @@ export declare class BladeClient {
39
40
  readonly models: ModelsResource;
40
41
  readonly sessions: SessionsResource;
41
42
  readonly chatProjects: ChatProjectsResource;
43
+ /**
44
+ * 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
45
+ * 只有内置 Web 的同源页面使用,第三方宿主页面不会被自动统计。
46
+ */
47
+ readonly siteVisits: SiteVisitsResource;
42
48
  /** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
43
49
  readonly hub: SessionHub;
44
50
  constructor(options: BladeClientOptions);
@@ -87,7 +93,6 @@ export declare class BladeClient {
87
93
  private buildHeaders;
88
94
  private buildUrl;
89
95
  private baseUrlWithTrailingSlash;
90
- private toBaseRelativePath;
91
96
  private isSameBackendUrl;
92
97
  private shouldRefreshFor401;
93
98
  private hasExplicitBearerToken;
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ export { SessionSetupError } from "./session/definition";
17
17
  export type { SessionConfig, SessionDefinition, SessionSetupStage, SkillDefinition, TextFile, SolutionDefinition, } from "./session/definition";
18
18
  export { createInitialSessionState, reconcileHistoricalUserTurns, reconcileOptimisticUserTurns, toReplaySnapshot, } from "./session/state";
19
19
  export type { AskUserAnswerData, AgentLoopInfo, ActiveCompactionState, ConnectionStatus, ReplaySnapshot, SessionState, } from "./session/state";
20
+ export { EMPTY_SESSION_QUEUE, canCancelQueuedMessage, canDeliverQueuedMessage, canEditQueuedMessage, isSessionQueueSnapshot, parseQueueAck, queuePauseReasonLabel, queuedMessageStatusLabel, shouldApplySnapshot, } from "./session/queue";
21
+ export type { QueueAck, QueueOperationResult, QueuedMessage, QueuedMessageStatus, SessionQueueSnapshot, } from "./session/queue";
20
22
  export { connectEmbedded } from "./commands/embedded";
21
23
  export type { EmbeddedChat, EmbeddedChatOptions } from "./commands/embedded";
22
24
  export type { CommandHandler } from "./commands/registry";
@@ -34,6 +36,7 @@ export type { SessionPlugin, SessionPluginActivation } from "./resources/session
34
36
  export type { SessionPluginConfigError, SessionPluginConfigSet, SessionPluginConfigState, SessionPluginConfigStateName, SessionPluginConfigSummary, SessionPluginConfigUpdate, } from "./resources/sessions";
35
37
  export { ChatProjectsResource } from "./resources/chat-projects";
36
38
  export type { ChatProject } from "./resources/chat-projects";
39
+ export type { SiteVisitsResource, SiteVisitsSummary } from "./resources/site-visits";
37
40
  export { hasChatRunEvent } from "./shared/projection";
38
41
  export type { CreateSessionRequest, AppCliAttachment, AppCliDefinition, FileEntry, ImportSessionOptions, PaginatedSessionsResult, GlobalSearchConversationResult, GlobalSearchFileResult, GlobalSearchResult, GlobalSearchResultItem, ResultFeedback, ResultFeedbackReason, HistoricalCommand, SessionContextStats, SessionHistory, SessionTurnsPage, ShareLinkResult, UploadFileEntry, UploadFilesOptions, } from "./resources/sessions";
39
42
  export type { ArchivedFileInfo, ArchivedToolCallInfo, ChatMessage, CompactionInfo, FileContentPart, ImageUrlContentPart, MemoryRefInfo, MessageContent, MessageContentPart, TextContentPart, ToolBridgeContent, ToolCallInfo, } from "./schemas/message";
package/dist/index.js CHANGED
@@ -167,6 +167,10 @@ function decorateAuthError(error) {
167
167
  }
168
168
  return error;
169
169
  }
170
+ function buildBackendUrl(baseUrl, path) {
171
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
172
+ return new URL(path.startsWith("/") ? path.slice(1) : path, base);
173
+ }
170
174
 
171
175
  // src/resources/auth.ts
172
176
  var AuthResource = class {
@@ -3131,6 +3135,43 @@ var ChatProjectsResource = class {
3131
3135
  }
3132
3136
  };
3133
3137
 
3138
+ // src/resources/site-visits.ts
3139
+ var SITE_VISITS_PATH = "/api/site-visits";
3140
+ var REQUEST_TIMEOUT_MS = 5e3;
3141
+ var SiteVisitsResource = class {
3142
+ constructor(client) {
3143
+ this.client = client;
3144
+ }
3145
+ client;
3146
+ /** 上报一次页面访问,返回当前快照;不承诺本次已计入或已落盘。 */
3147
+ record() {
3148
+ return this.request("POST");
3149
+ }
3150
+ /** 只读快照:不设置访客 Cookie,也不增加任何计数。 */
3151
+ summary() {
3152
+ return this.request("GET");
3153
+ }
3154
+ async request(method) {
3155
+ const url = buildBackendUrl(this.client.options.baseUrl, SITE_VISITS_PATH);
3156
+ const controller = new AbortController();
3157
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
3158
+ try {
3159
+ const response = await (this.client.options.fetchImpl ?? fetch)(url.toString(), {
3160
+ method,
3161
+ // 访客 Cookie 是唯一的去重依据,必须随请求往返。
3162
+ credentials: "include",
3163
+ signal: controller.signal
3164
+ });
3165
+ if (!response.ok) {
3166
+ throw new BladeApiError(response, await extractErrorDetail(response));
3167
+ }
3168
+ return await response.json();
3169
+ } finally {
3170
+ clearTimeout(timeout);
3171
+ }
3172
+ }
3173
+ };
3174
+
3134
3175
  // src/shared/auth-busy.ts
3135
3176
  var DEFAULT_RETRY_AFTER_SECONDS = 1;
3136
3177
  var MAX_TIMER_DELAY_MS = 2147483647;
@@ -3310,6 +3351,123 @@ function replaceWindow(current, page, liveRevisionAtStart, liveRevisions) {
3310
3351
  ]);
3311
3352
  }
3312
3353
 
3354
+ // src/session/queue.ts
3355
+ var EMPTY_SESSION_QUEUE = {
3356
+ session_id: "",
3357
+ revision: 0,
3358
+ paused: false,
3359
+ pause_reason: null,
3360
+ items: []
3361
+ };
3362
+ var QUEUE_FAILURE_MESSAGE = "\u961F\u5217\u64CD\u4F5C\u5931\u8D25";
3363
+ var QUEUED_MESSAGE_STATUSES = [
3364
+ "pending",
3365
+ "delivering",
3366
+ "claimed",
3367
+ "consumed",
3368
+ "cancelled"
3369
+ ];
3370
+ function toQueuedMessage(value) {
3371
+ if (!isRecord(value)) return null;
3372
+ const status = typeof value.status === "string" && QUEUED_MESSAGE_STATUSES.includes(value.status) ? value.status : null;
3373
+ if (typeof value.id !== "string" || typeof value.content !== "string" || typeof value.position !== "number" || typeof value.version !== "number" || typeof value.created_at !== "string" || typeof value.updated_at !== "string" || status === null) {
3374
+ return null;
3375
+ }
3376
+ const item = {
3377
+ id: value.id,
3378
+ content: value.content,
3379
+ position: value.position,
3380
+ version: value.version,
3381
+ status,
3382
+ created_at: value.created_at,
3383
+ updated_at: value.updated_at
3384
+ };
3385
+ if (typeof value.target_run_id === "string") item.target_run_id = value.target_run_id;
3386
+ if (typeof value.error === "string") item.error = value.error;
3387
+ return item;
3388
+ }
3389
+ function toQueueSnapshot(value, fallbackSessionId) {
3390
+ if (!isRecord(value)) return null;
3391
+ if (typeof value.revision !== "number" || !Number.isInteger(value.revision) || value.revision < 0) {
3392
+ return null;
3393
+ }
3394
+ if (typeof value.paused !== "boolean") return null;
3395
+ if (!Array.isArray(value.items)) return null;
3396
+ const pauseReason = value.pause_reason;
3397
+ if (pauseReason !== null && pauseReason !== void 0 && typeof pauseReason !== "string") {
3398
+ return null;
3399
+ }
3400
+ const items = [];
3401
+ for (const raw of value.items) {
3402
+ const item = toQueuedMessage(raw);
3403
+ if (!item) return null;
3404
+ items.push(item);
3405
+ }
3406
+ return {
3407
+ session_id: typeof value.session_id === "string" && value.session_id ? value.session_id : fallbackSessionId,
3408
+ revision: value.revision,
3409
+ paused: value.paused,
3410
+ pause_reason: typeof pauseReason === "string" ? pauseReason : null,
3411
+ items
3412
+ };
3413
+ }
3414
+ function isSessionQueueSnapshot(value) {
3415
+ if (!isRecord(value) || typeof value.session_id !== "string") return false;
3416
+ return toQueueSnapshot(value, value.session_id) !== null;
3417
+ }
3418
+ function shouldApplySnapshot(current, next) {
3419
+ if (!isSessionQueueSnapshot(next)) return false;
3420
+ if (current?.session_id && next.session_id !== current.session_id) return false;
3421
+ return next.revision > (current?.revision ?? -1);
3422
+ }
3423
+ function parseQueueAck(response, fallbackSessionId) {
3424
+ if (!isRecord(response)) {
3425
+ return { ok: false, conflict: false, snapshot: null, message: QUEUE_FAILURE_MESSAGE };
3426
+ }
3427
+ const message = typeof response.message === "string" && response.message ? response.message : null;
3428
+ const status = response.status;
3429
+ if (status !== "ok" && status !== "conflict") {
3430
+ return { ok: false, conflict: false, snapshot: null, message: message ?? QUEUE_FAILURE_MESSAGE };
3431
+ }
3432
+ const snapshot2 = toQueueSnapshot(response, fallbackSessionId);
3433
+ if (status === "conflict") {
3434
+ return { ok: false, conflict: true, snapshot: snapshot2, message: message ?? QUEUE_FAILURE_MESSAGE };
3435
+ }
3436
+ if (!snapshot2) {
3437
+ return { ok: false, conflict: false, snapshot: null, message: message ?? QUEUE_FAILURE_MESSAGE };
3438
+ }
3439
+ return { ok: true, conflict: false, snapshot: snapshot2, message };
3440
+ }
3441
+ function canEditQueuedMessage(item) {
3442
+ return item.status === "pending";
3443
+ }
3444
+ function canCancelQueuedMessage(item) {
3445
+ return item.status === "pending";
3446
+ }
3447
+ function canDeliverQueuedMessage(item) {
3448
+ return item.status === "pending";
3449
+ }
3450
+ var QUEUED_MESSAGE_STATUS_LABELS = {
3451
+ pending: "\u5F85\u6267\u884C",
3452
+ delivering: "\u7B49\u5F85\u63A5\u6536",
3453
+ claimed: "",
3454
+ consumed: "",
3455
+ cancelled: ""
3456
+ };
3457
+ function queuedMessageStatusLabel(status) {
3458
+ return QUEUED_MESSAGE_STATUS_LABELS[status] ?? "";
3459
+ }
3460
+ var QUEUE_PAUSE_REASON_LABELS = {
3461
+ user_stop: "\u5DF2\u6682\u505C",
3462
+ run_failed: "\u4EFB\u52A1\u5F02\u5E38\u5DF2\u6682\u505C",
3463
+ run_unknown: "\u4EFB\u52A1\u7ED3\u675F\u72B6\u6001\u672A\u77E5\uFF0C\u5DF2\u6682\u505C",
3464
+ server_restart: "\u670D\u52A1\u91CD\u542F\u5DF2\u6682\u505C"
3465
+ };
3466
+ function queuePauseReasonLabel(reason) {
3467
+ if (!reason) return null;
3468
+ return QUEUE_PAUSE_REASON_LABELS[reason] ?? reason;
3469
+ }
3470
+
3313
3471
  // src/schemas/mcp-app.ts
3314
3472
  function isMcpAppData(value) {
3315
3473
  return isRecord(value) && typeof value.sourceId === "string" && value.sourceId.length > 0 && typeof value.resourceUri === "string" && value.resourceUri.startsWith("ui://") && typeof value.mimeType === "string" && isRecord(value.resourceMeta) && isRecord(value.toolInput) && isRecord(value.toolResult);
@@ -3608,6 +3766,7 @@ function createInitialSessionState(sessionId) {
3608
3766
  agentLoops: {},
3609
3767
  activeCompaction: null,
3610
3768
  replay: null,
3769
+ queue: EMPTY_SESSION_QUEUE,
3611
3770
  viewerRole: null,
3612
3771
  nextBefore: null,
3613
3772
  loadingOlder: false
@@ -4015,6 +4174,10 @@ function withTurns(state, turns) {
4015
4174
  status: isWaitingForInput ? "waiting_for_input" : state.status
4016
4175
  };
4017
4176
  }
4177
+ function withQueue(state, snapshot2) {
4178
+ if (!shouldApplySnapshot(state.queue, snapshot2)) return state;
4179
+ return { ...state, queue: snapshot2 };
4180
+ }
4018
4181
  var localTurnCounter = 0;
4019
4182
  function addUserMessage(state, content, clientRequestId) {
4020
4183
  localTurnCounter += 1;
@@ -4092,6 +4255,7 @@ function markStreamingTurns(state, turnStatus, toolCallStatus) {
4092
4255
  var OOM_MESSAGE = "\u6C99\u76D2\u5185\u5B58\u4F7F\u7528\u8D85\u51FA\u9650\u5236\uFF0C\u5DF2\u81EA\u52A8\u91CD\u542F\u3002\u5982\u679C\u7ECF\u5E38\u89E6\u53D1\uFF0C\u53EF\u4EE5\u8054\u7CFB\u7BA1\u7406\u5458\u8C03\u6574\u989D\u5EA6\u3002";
4093
4256
  var COMMAND_RESTORE_RETRY_BASE_MS = 1e3;
4094
4257
  var COMMAND_RESTORE_RETRY_MAX_MS = 3e4;
4258
+ var QUEUE_ACK_TIMEOUT_MS = 1e4;
4095
4259
  var fallbackRequestCounter = 0;
4096
4260
  function createClientRequestId() {
4097
4261
  if (typeof globalThis.crypto?.randomUUID === "function") {
@@ -4138,6 +4302,14 @@ function isToolBridgeContent(value) {
4138
4302
  function isCursorInvalidError(error) {
4139
4303
  return typeof error === "object" && error !== null && "status" in error && error.status === 409;
4140
4304
  }
4305
+ function queueRequestFailure(error) {
4306
+ return {
4307
+ ok: false,
4308
+ conflict: false,
4309
+ snapshot: null,
4310
+ message: error instanceof Error && error.message ? error.message : "\u961F\u5217\u64CD\u4F5C\u5931\u8D25"
4311
+ };
4312
+ }
4141
4313
  function commandDeliveryKey(deliveryId) {
4142
4314
  return `tool-command:${deliveryId}`;
4143
4315
  }
@@ -4180,8 +4352,6 @@ var AgentSession = class _AgentSession {
4180
4352
  modeRevision = 0;
4181
4353
  liveRevisions = /* @__PURE__ */ new Map();
4182
4354
  pendingOptimisticTurnIds = /* @__PURE__ */ new Set();
4183
- /** Consecutive supplements are persisted as one authoritative user turn. */
4184
- pendingAppendTurnId = null;
4185
4355
  olderPending = null;
4186
4356
  replaceInFlight = 0;
4187
4357
  // 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
@@ -4354,21 +4524,95 @@ var AgentSession = class _AgentSession {
4354
4524
  this.sendPending = false;
4355
4525
  }
4356
4526
  }
4357
- /** Queue a plain-text prompt while a turn is streaming. */
4358
- async queue(content) {
4527
+ /**
4528
+ * 入队一条等待消息(运行中排队 / 暂停期间照常入队)。
4529
+ *
4530
+ * ack 超时或传输失败时结果未知:用**同一个** `client_request_id` 重试一次,
4531
+ * 服务端按 `(session_id, client_request_id)` 幂等,不会制造第二条。
4532
+ */
4533
+ async queueMessage(content) {
4359
4534
  const text = content.trim();
4360
- if (!text) return false;
4361
- this.runtime.ensureConnected();
4362
- this.runtime.emitEvent("chat:append", { session_id: this.sessionId, message: text });
4363
- return true;
4535
+ if (!text) {
4536
+ return { ok: false, conflict: false, snapshot: null, message: "\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A" };
4537
+ }
4538
+ const payload = {
4539
+ session_id: this.sessionId,
4540
+ message: text,
4541
+ client_request_id: createClientRequestId()
4542
+ };
4543
+ try {
4544
+ return await this.requestQueueAck("chat:append", payload);
4545
+ } catch {
4546
+ try {
4547
+ return await this.requestQueueAck("chat:append", payload);
4548
+ } catch (error) {
4549
+ return queueRequestFailure(error);
4550
+ }
4551
+ }
4364
4552
  }
4365
- async listQueue() {
4366
- const response = await this.runtime.emitWithAck("chat:queue:list", { session_id: this.sessionId }, 1e4);
4367
- return Array.isArray(response?.items) ? response.items : [];
4553
+ /** 修改一条等待消息的正文;`expectedVersion` 是客户端看到的条目版本。 */
4554
+ async updateQueuedMessage(itemId, expectedVersion, content) {
4555
+ return this.runQueueCommand("chat:queue:update", {
4556
+ session_id: this.sessionId,
4557
+ item_id: itemId,
4558
+ expected_version: expectedVersion,
4559
+ message: content
4560
+ });
4368
4561
  }
4369
- async cancelQueued(id) {
4370
- const response = await this.runtime.emitWithAck("chat:queue:cancel", { session_id: this.sessionId, id }, 1e4);
4371
- return response?.cancelled === true;
4562
+ /**
4563
+ * 重排等待消息。`orderedIds` 必须是当前 pending 条目 id 的**完整集合**(顺序任意),
4564
+ * 服务端按 `expectedRevision` 校验后整体替换顺序。
4565
+ */
4566
+ async reorderQueuedMessages(expectedRevision, orderedIds) {
4567
+ return this.runQueueCommand("chat:queue:reorder", {
4568
+ session_id: this.sessionId,
4569
+ expected_revision: expectedRevision,
4570
+ ordered_ids: orderedIds
4571
+ });
4572
+ }
4573
+ /** 删除一条等待消息;不传版本时由服务端自行判定。 */
4574
+ async cancelQueuedMessage(itemId, expectedVersion) {
4575
+ return this.runQueueCommand("chat:queue:cancel", {
4576
+ session_id: this.sessionId,
4577
+ item_id: itemId,
4578
+ expected_version: expectedVersion
4579
+ });
4580
+ }
4581
+ /** 立即补充:把等待消息交给当前运行,在下一个接收点消费,不停止也不另起一轮。 */
4582
+ async deliverQueuedMessage(itemId, expectedVersion) {
4583
+ return this.runQueueCommand("chat:queue:deliver", {
4584
+ session_id: this.sessionId,
4585
+ item_id: itemId,
4586
+ expected_version: expectedVersion
4587
+ });
4588
+ }
4589
+ /** 继续执行:清除暂停,并按当前顺序恢复逐条接续。 */
4590
+ async resumeQueue() {
4591
+ return this.runQueueCommand("chat:queue:resume", { session_id: this.sessionId });
4592
+ }
4593
+ /** 拉取一份权威队列快照。首次连接与每次重连后由实例自动调用,也可以手动刷新。 */
4594
+ async refreshQueue() {
4595
+ return this.runQueueCommand("chat:queue:list", { session_id: this.sessionId });
4596
+ }
4597
+ /**
4598
+ * 发出一条队列命令并安装 ack 里的快照。
4599
+ * 传输层失败(超时 / 断线)会 reject,由调用方决定是否重试。
4600
+ */
4601
+ async requestQueueAck(event, payload) {
4602
+ this.runtime.ensureConnected();
4603
+ const response = await this.runtime.emitWithAck(event, payload, QUEUE_ACK_TIMEOUT_MS);
4604
+ const result = parseQueueAck(response, this.sessionId);
4605
+ const snapshot2 = result.snapshot;
4606
+ if (snapshot2) this.update((state) => withQueue(state, snapshot2));
4607
+ return result;
4608
+ }
4609
+ /** 队列命令的对外入口:传输失败只体现为失败结果,不向调用方抛异常。 */
4610
+ async runQueueCommand(event, payload) {
4611
+ try {
4612
+ return await this.requestQueueAck(event, payload);
4613
+ } catch (error) {
4614
+ return queueRequestFailure(error);
4615
+ }
4372
4616
  }
4373
4617
  async sendUnlocked(content, options) {
4374
4618
  await this.ensureIdleBeforeSend();
@@ -4494,40 +4738,7 @@ var AgentSession = class _AgentSession {
4494
4738
  if (optimisticTurnId) {
4495
4739
  this.pendingOptimisticTurnIds.delete(optimisticTurnId);
4496
4740
  this.liveRevisions.delete(optimisticTurnId);
4497
- if (this.pendingAppendTurnId === optimisticTurnId) this.pendingAppendTurnId = null;
4498
- }
4499
- }
4500
- /** 智能体运行期间追加补充说明(不打断当前回复)。 */
4501
- append(text) {
4502
- this.runtime.ensureConnected();
4503
- const existingAppendId = this.pendingAppendTurnId;
4504
- let optimisticTurnId = existingAppendId;
4505
- this.update((state) => {
4506
- const existing = existingAppendId ? state.turns.find((turn) => turn.turn_id === existingAppendId) : void 0;
4507
- if (existing && existing.role === "user") {
4508
- const blocks = existing.blocks.map(
4509
- (block, index) => index === 0 && block.type === "text" ? { ...block, content: `${block.content}
4510
- ${text}` } : block
4511
- );
4512
- return withTurns(state, state.turns.map(
4513
- (turn) => turn.turn_id === existingAppendId ? { ...turn, blocks } : turn
4514
- ));
4515
- }
4516
- const next = addUserMessage(state, text, createClientRequestId());
4517
- optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
4518
- return next;
4519
- });
4520
- if (optimisticTurnId) {
4521
- this.pendingAppendTurnId = optimisticTurnId;
4522
- if (!this.pendingOptimisticTurnIds.has(optimisticTurnId)) {
4523
- this.markOptimisticTurnPending(optimisticTurnId);
4524
- }
4525
4741
  }
4526
- this.runtime.emitEvent("chat:append", {
4527
- session_id: this.sessionId,
4528
- message: text,
4529
- ...optimisticTurnId ? { client_request_id: optimisticTurnId.slice("local-user-".length) } : {}
4530
- });
4531
4742
  }
4532
4743
  /** 停止当前回复。 */
4533
4744
  stop() {
@@ -4621,6 +4832,15 @@ ${text}` } : block
4621
4832
  if (this.state.connection === connection) return;
4622
4833
  this.update((s) => ({ ...s, connection }));
4623
4834
  }
4835
+ /**
4836
+ * @internal 服务端广播的权威队列快照。
4837
+ * 结构不合法或 revision 不新的一律丢弃,绝不用旧快照覆盖新状态。
4838
+ */
4839
+ _handleQueueUpdated(payload) {
4840
+ if (!isSessionQueueSnapshot(payload)) return;
4841
+ const snapshot2 = payload;
4842
+ this.update((state) => withQueue(state, snapshot2));
4843
+ }
4624
4844
  /** @internal socket 重连后由 Hub 调用:重新加入房间并补数。 */
4625
4845
  _rejoinAfterReconnect() {
4626
4846
  this.timelineEpoch += 1;
@@ -4663,7 +4883,7 @@ ${text}` } : block
4663
4883
  this.subscriptionReplaySource = null;
4664
4884
  }
4665
4885
  this.scheduleHistoricalCommandRestore(commandRecoveryGeneration);
4666
- await this.syncReplayFromSession();
4886
+ await Promise.all([this.syncReplayFromSession(), this.refreshQueue()]);
4667
4887
  });
4668
4888
  }
4669
4889
  /** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
@@ -5507,6 +5727,7 @@ var SessionHub = class {
5507
5727
  if (isReconnect) {
5508
5728
  for (const session of this.sessions.values()) {
5509
5729
  void session._rejoinAfterReconnect();
5730
+ void session.refreshQueue();
5510
5731
  }
5511
5732
  }
5512
5733
  });
@@ -5568,6 +5789,9 @@ var SessionHub = class {
5568
5789
  socket.on("system:error", (data) => {
5569
5790
  for (const session of this.route(data.session_id)) session._handleSystemError(data.message);
5570
5791
  });
5792
+ socket.on("chat:queue:updated", (data) => {
5793
+ for (const session of this.route(data?.session_id)) session._handleQueueUpdated(data);
5794
+ });
5571
5795
  socket.on(
5572
5796
  "system:notification",
5573
5797
  (data) => {
@@ -5711,6 +5935,11 @@ var BladeClient = class {
5711
5935
  models;
5712
5936
  sessions;
5713
5937
  chatProjects;
5938
+ /**
5939
+ * 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
5940
+ * 只有内置 Web 的同源页面使用,第三方宿主页面不会被自动统计。
5941
+ */
5942
+ siteVisits;
5714
5943
  /** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
5715
5944
  hub;
5716
5945
  constructor(options) {
@@ -5724,6 +5953,7 @@ var BladeClient = class {
5724
5953
  this.models = new ModelsResource(this);
5725
5954
  this.sessions = new SessionsResource(this);
5726
5955
  this.chatProjects = new ChatProjectsResource(this);
5956
+ this.siteVisits = new SiteVisitsResource(this);
5727
5957
  this.hub = new SessionHub(this);
5728
5958
  if (this.options.token === void 0) {
5729
5959
  this.runtimeToken = readStoredToken(this.options.baseUrl);
@@ -5894,7 +6124,7 @@ var BladeClient = class {
5894
6124
  return response;
5895
6125
  }
5896
6126
  buildAuthedUrl(path) {
5897
- const url = new URL(this.toBaseRelativePath(path), this.baseUrlWithTrailingSlash());
6127
+ const url = buildBackendUrl(this.options.baseUrl, path);
5898
6128
  const token = this.resolveTokenForUrl(url);
5899
6129
  if (token) {
5900
6130
  url.searchParams.set("token", token);
@@ -5913,14 +6143,11 @@ var BladeClient = class {
5913
6143
  return result;
5914
6144
  }
5915
6145
  buildUrl(path) {
5916
- return new URL(this.toBaseRelativePath(path), this.baseUrlWithTrailingSlash());
6146
+ return buildBackendUrl(this.options.baseUrl, path);
5917
6147
  }
5918
6148
  baseUrlWithTrailingSlash() {
5919
6149
  return this.options.baseUrl.endsWith("/") ? this.options.baseUrl : `${this.options.baseUrl}/`;
5920
6150
  }
5921
- toBaseRelativePath(path) {
5922
- return path.startsWith("/") ? path.slice(1) : path;
5923
- }
5924
6151
  isSameBackendUrl(url) {
5925
6152
  const base = new URL(this.baseUrlWithTrailingSlash());
5926
6153
  if (url.origin !== base.origin) {
@@ -6194,7 +6421,7 @@ function resolveServiceUrl(endpoints, name, path = "") {
6194
6421
 
6195
6422
  // src/version.ts
6196
6423
  var SDK_NAME = "agent-client";
6197
- var SDK_VERSION = true ? "2610.0.0-beta.51" : "1.1.1";
6424
+ var SDK_VERSION = true ? "2610.0.0-beta.53" : "1.1.1";
6198
6425
 
6199
6426
  // src/commands/protocol.ts
6200
6427
  function isCommandEnvelope(value) {
@@ -6304,6 +6531,7 @@ export {
6304
6531
  ComputersResource,
6305
6532
  DEFAULT_REPLAY_SPEED,
6306
6533
  EMPTY_PLATFORM_ENDPOINTS,
6534
+ EMPTY_SESSION_QUEUE,
6307
6535
  LayoutType,
6308
6536
  ModelsResource,
6309
6537
  PollingBackoff,
@@ -6320,6 +6548,9 @@ export {
6320
6548
  authBusyRetryDelayMs,
6321
6549
  buildMessageContent,
6322
6550
  buildToolUiCardKey,
6551
+ canCancelQueuedMessage,
6552
+ canDeliverQueuedMessage,
6553
+ canEditQueuedMessage,
6323
6554
  canToggleComputer,
6324
6555
  chatErrorForDisplay,
6325
6556
  collectInlineToolUiCards,
@@ -6348,16 +6579,21 @@ export {
6348
6579
  isInboundEnvelope,
6349
6580
  isInternalStatusToolUiCard,
6350
6581
  isMcpAppData,
6582
+ isSessionQueueSnapshot,
6351
6583
  isToolUiCard,
6352
6584
  latestPostChatFollowup,
6353
6585
  loadPlatformEndpoints,
6354
6586
  normalizeMessageContent,
6587
+ parseQueueAck,
6355
6588
  prependOlder,
6589
+ queuePauseReasonLabel,
6590
+ queuedMessageStatusLabel,
6356
6591
  reconcileHistoricalUserTurns,
6357
6592
  reconcileOptimisticUserTurns,
6358
6593
  replaceWindow,
6359
6594
  resolveServiceUrl,
6360
6595
  resolveToolUiCardContent,
6596
+ shouldApplySnapshot,
6361
6597
  sortComputers,
6362
6598
  toReplaySnapshot,
6363
6599
  transformSlashCommand