@blade-hq/agent-client 2610.0.0-beta.47 → 2610.0.0-beta.48

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
@@ -502,6 +502,15 @@ PATCH / query / FormData / AbortSignal 等完整 HTTP 语义(否则不够用
502
502
  > **安全约束**:`client.token` 只应发往 `baseUrl` 同源的接口。不要把它附加到
503
503
  > 第三方域名的请求上——那等于把用户的访问凭据交给别人。
504
504
 
505
+ ### 只读轮询退避
506
+
507
+ `new PollingBackoff(3000)` 为轮询维护独立退避状态,参数为正常间隔(大于 0、不超过 60 秒)。
508
+ 请求成功后调用 `reset()`,失败后调用 `failed(error)`;`waitMs()` 返回剩余等待毫秒数,
509
+ `0` 表示可以请求,`false` 表示停止。HTTP 错误应传入保留响应头的 `BladeApiError`。
510
+ 401/403 等确定性错误停止,网络/5xx 指数退避带抖动且最高 60 秒;`Retry-After`
511
+ 可以要求更长等待,超出浏览器定时器范围时停止。策略自身不发送请求,调度器须传递
512
+ AbortSignal、在卸载时停止,并避免将它用于自动重放 POST 等有副作用的操作。
513
+
505
514
  ## headless:一次性问答
506
515
 
507
516
  ```ts
@@ -607,7 +616,7 @@ transformSlashCommand(skillId, prompt, { local: false, installed: false })
607
616
  - **SDK 身份**:`SDK_NAME`、`SDK_VERSION`
608
617
  - **声明式会话**:`SessionDefinition`、`SolutionDefinition`、`SkillDefinition`、`SessionConfig`、`TextFile`、`SessionSetupError`、`SessionSetupStage`
609
618
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
610
- - **聊天项目资源(REST)**:`ChatProjectsResource`、`ChatProject`
619
+ - **聊天项目资源(REST)**:`ChatProjectsResource`、`ChatProject`(含 `delete` 永久删除)
611
620
  - **会话资源(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`
612
621
  - **会话回放**:`ReplayState`、`ReplaySpeed`、`ReplayPreview`、`ReplaySnapshot`、`toReplaySnapshot`、`DEFAULT_REPLAY_SPEED`
613
622
  - **会话状态机**:`SessionHub`、`SessionConnectOptions`、`SessionState`、`SendOptions`、`ConnectionStatus`、`AskUserAnswerData`、`AgentLoopInfo`、`ActiveCompactionState`、`createInitialSessionState`、`AgentSessionEventName`
@@ -615,4 +624,4 @@ transformSlashCommand(skillId, prompt, { local: false, installed: false })
615
624
  - **消息与投影协议**:`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`
616
625
  - **Solution / 任务协议**:`Solution`、`SolutionAppField`、`SolutionAppState`、`SolutionAppUiConfig`、`SolutionRef`、`PublishedSolutionRef`、`ExistingSolutionRef`、`PreparedSolution`、`PreparedSolutionAsset`、`LayoutType`、`BizRole`、`TaskStatus`、`BackgroundTask`、`BackgroundTaskStopResult`
617
626
  - **Headless**:`HeadlessResource`、`RunOptions`、`RunResult`、`RunTrace`
618
- - **低层通道(apps/web 等高级集成)**:`createSocket`、`CreateSocketOptions`、`TypedSocket`、`AsrAudioPayload`、`ClientProjectionBuilder`、`RawEvent`、`acknowledgeTurnEvents`、`hasChatRunEvent`、`acceptedPostChatFollowupCompletesLatestRun`、`reconcileOptimisticUserTurns`、`reconcileHistoricalUserTurns`
627
+ - **低层通道(apps/web 等高级集成)**:`createSocket`、`CreateSocketOptions`、`TypedSocket`、`AsrAudioPayload`、`AuthBusyReconnect`、`authBusyRetryDelayMs`、`ClientProjectionBuilder`、`RawEvent`、`acknowledgeTurnEvents`、`hasChatRunEvent`、`acceptedPostChatFollowupCompletesLatestRun`、`reconcileOptimisticUserTurns`、`reconcileHistoricalUserTurns`
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { BladeClient } from "./blade-client";
2
2
  export type { BladeClientOptions, UploadProgress } from "./blade-client";
3
3
  export { BladeApiError } from "./rest";
4
+ export { PollingBackoff } from "./polling-backoff";
4
5
  export type { LoginOptions, LoginResult, TokenStorageMode } from "./auth-login";
5
6
  export { EMPTY_PLATFORM_ENDPOINTS, loadPlatformEndpoints, resolveServiceUrl, } from "./platform-endpoints";
6
7
  export type { LoadPlatformEndpointsOptions, PlatformEndpoints, PlatformServiceName, } from "./platform-endpoints";
@@ -50,6 +51,7 @@ export { Task, TaskStatus } from "./schemas/task";
50
51
  export type { BackgroundTask, BackgroundTaskStopResult } from "./schemas/background";
51
52
  export { acceptedPostChatFollowupCompletesLatestRun, acknowledgeTurnEvents, ClientProjectionBuilder, } from "./shared/projection";
52
53
  export type { RawEvent } from "./shared/projection";
54
+ export { AuthBusyReconnect, authBusyRetryDelayMs } from "./shared/auth-busy";
53
55
  export { createSocket } from "./socket";
54
56
  export type { CreateSocketOptions, TypedSocket } from "./socket";
55
57
  export type { AsrAudioPayload } from "./types/socket-events";
package/dist/index.js CHANGED
@@ -3053,6 +3053,50 @@ var ChatProjectsResource = class {
3053
3053
  update(projectId, payload) {
3054
3054
  return this.client.json("PATCH", `/api/chat-projects/${encodeURIComponent(projectId)}`, payload);
3055
3055
  }
3056
+ delete(projectId) {
3057
+ return this.client.json("DELETE", `/api/chat-projects/${encodeURIComponent(projectId)}`);
3058
+ }
3059
+ };
3060
+
3061
+ // src/shared/auth-busy.ts
3062
+ var DEFAULT_RETRY_AFTER_SECONDS = 1;
3063
+ var MAX_TIMER_DELAY_MS = 2147483647;
3064
+ function errorData(error) {
3065
+ if (typeof error !== "object" || error === null || !("data" in error)) return null;
3066
+ const data = error.data;
3067
+ return typeof data === "object" && data !== null ? data : null;
3068
+ }
3069
+ function authBusyRetryDelayMs(error) {
3070
+ const data = errorData(error);
3071
+ if (data?.code !== "auth_busy" || Number(data.status) !== 429) return null;
3072
+ const rawRetryAfter = data.retry_after;
3073
+ const seconds = rawRetryAfter == null || String(rawRetryAfter).trim() === "" ? Number.NaN : Number(rawRetryAfter);
3074
+ const delaySeconds = Number.isFinite(seconds) && seconds >= 0 ? seconds : DEFAULT_RETRY_AFTER_SECONDS;
3075
+ return Math.min(delaySeconds * 1e3, MAX_TIMER_DELAY_MS);
3076
+ }
3077
+ var AuthBusyReconnect = class {
3078
+ timer = null;
3079
+ get pending() {
3080
+ return this.timer !== null;
3081
+ }
3082
+ schedule(error, reconnect) {
3083
+ const delay = authBusyRetryDelayMs(error);
3084
+ if (delay === null) {
3085
+ this.cancel();
3086
+ return false;
3087
+ }
3088
+ this.cancel();
3089
+ this.timer = globalThis.setTimeout(() => {
3090
+ this.timer = null;
3091
+ reconnect();
3092
+ }, delay);
3093
+ return true;
3094
+ }
3095
+ cancel() {
3096
+ if (this.timer === null) return;
3097
+ globalThis.clearTimeout(this.timer);
3098
+ this.timer = null;
3099
+ }
3056
3100
  };
3057
3101
 
3058
3102
  // src/commands/registry.ts
@@ -5099,6 +5143,7 @@ var SessionHub = class {
5099
5143
  connectionListeners = /* @__PURE__ */ new Set();
5100
5144
  bound = false;
5101
5145
  hasConnected = false;
5146
+ authBusyReconnect = new AuthBusyReconnect();
5102
5147
  constructor(client) {
5103
5148
  this.client = client;
5104
5149
  }
@@ -5165,6 +5210,7 @@ var SessionHub = class {
5165
5210
  /** 手动重连(例如登录态变化后)。 */
5166
5211
  reconnect() {
5167
5212
  this.ensureBound();
5213
+ this.authBusyReconnect.cancel();
5168
5214
  const socket = this.client.socket();
5169
5215
  socket.disconnect();
5170
5216
  this.setConnection("connecting");
@@ -5172,11 +5218,13 @@ var SessionHub = class {
5172
5218
  }
5173
5219
  /** 断开连接(不清理 session 实例)。 */
5174
5220
  disconnect() {
5221
+ this.authBusyReconnect.cancel();
5175
5222
  this.client.socket().disconnect();
5176
5223
  this.setConnection("disconnected");
5177
5224
  }
5178
5225
  /** 清理当前身份下缓存的全部会话,避免切换登录身份后复用旧快照。 */
5179
5226
  disposeAll() {
5227
+ this.authBusyReconnect.cancel();
5180
5228
  this.bootstrapPending.clear();
5181
5229
  for (const session of [...this.sessions.values()]) {
5182
5230
  this.forceRelease(session);
@@ -5188,6 +5236,7 @@ var SessionHub = class {
5188
5236
  * turn:* / chat:* 事件都会静默丢弃。
5189
5237
  */
5190
5238
  _handleSocketReplaced() {
5239
+ this.authBusyReconnect.cancel();
5191
5240
  this.bound = false;
5192
5241
  this.setConnection("disconnected");
5193
5242
  if (this.sessions.size === 0) return;
@@ -5240,6 +5289,9 @@ var SessionHub = class {
5240
5289
  if (this.sessions.get(session.sessionId) === session) {
5241
5290
  this.sessions.delete(session.sessionId);
5242
5291
  }
5292
+ if (this.sessions.size === 0) {
5293
+ this.authBusyReconnect.cancel();
5294
+ }
5243
5295
  ;
5244
5296
  this.client.socket().emit("session:unsubscribe", { session_id: session.sessionId });
5245
5297
  return true;
@@ -5248,10 +5300,13 @@ var SessionHub = class {
5248
5300
  forceRelease(session) {
5249
5301
  this.holdCounts.delete(session.sessionId);
5250
5302
  session.dispose();
5303
+ if (this.sessions.size === 0) {
5304
+ this.authBusyReconnect.cancel();
5305
+ }
5251
5306
  }
5252
5307
  ensureConnected() {
5253
5308
  const socket = this.client.socket();
5254
- if (socket.connected || socket.active) return;
5309
+ if (socket.connected || socket.active || this.authBusyReconnect.pending) return;
5255
5310
  this.setConnection("connecting");
5256
5311
  socket.connect();
5257
5312
  }
@@ -5283,6 +5338,7 @@ var SessionHub = class {
5283
5338
  this.bound = true;
5284
5339
  const socket = this.client.socket();
5285
5340
  socket.on("connect", () => {
5341
+ this.authBusyReconnect.cancel();
5286
5342
  const isReconnect = this.hasConnected;
5287
5343
  this.hasConnected = true;
5288
5344
  this.setConnection("connected");
@@ -5292,8 +5348,24 @@ var SessionHub = class {
5292
5348
  }
5293
5349
  }
5294
5350
  });
5295
- socket.on("connect_error", () => this.setConnection("disconnected"));
5296
- socket.on("disconnect", () => this.setConnection("disconnected"));
5351
+ socket.on("connect_error", (error) => {
5352
+ this.setConnection("disconnected");
5353
+ if (this.sessions.size === 0) {
5354
+ this.authBusyReconnect.cancel();
5355
+ return;
5356
+ }
5357
+ if (this.authBusyReconnect.schedule(error, () => {
5358
+ const current = this.client.socket();
5359
+ if (current !== socket || current.connected || current.active) return;
5360
+ this.setConnection("connecting");
5361
+ current.connect();
5362
+ })) return;
5363
+ this.authBusyReconnect.cancel();
5364
+ });
5365
+ socket.on("disconnect", () => {
5366
+ this.authBusyReconnect.cancel();
5367
+ this.setConnection("disconnected");
5368
+ });
5297
5369
  socket.io.on("reconnect_attempt", () => this.setConnection("reconnecting"));
5298
5370
  socket.on("turn:start", (data) => {
5299
5371
  for (const session of this.route(data.session_id)) session._handleTurnStart(data);
@@ -5842,6 +5914,42 @@ function parseXhrHeaders(rawHeaders) {
5842
5914
  return headers;
5843
5915
  }
5844
5916
 
5917
+ // src/polling-backoff.ts
5918
+ var MAX_BACKOFF_MS = 6e4;
5919
+ var MAX_TIMER_MS = 2147483647;
5920
+ var PollingBackoff = class {
5921
+ constructor(intervalMs) {
5922
+ this.intervalMs = intervalMs;
5923
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0 || intervalMs > MAX_BACKOFF_MS) {
5924
+ throw new RangeError("Polling interval must be between 0 and 60000 ms");
5925
+ }
5926
+ }
5927
+ intervalMs;
5928
+ failures = 0;
5929
+ nextAt = 0;
5930
+ reset() {
5931
+ this.failures = 0;
5932
+ this.nextAt = 0;
5933
+ }
5934
+ failed(error) {
5935
+ if (error instanceof Error && error.name === "AbortError" || error instanceof BladeApiError && error.status < 500 && error.status !== 408 && error.status !== 429) {
5936
+ this.nextAt = false;
5937
+ return;
5938
+ }
5939
+ this.failures = Math.min(this.failures + 1, 10);
5940
+ const ceiling = Math.min(MAX_BACKOFF_MS, this.intervalMs * 2 ** this.failures);
5941
+ const backoff = Math.max(this.intervalMs, ceiling * (0.5 + Math.random() * 0.5));
5942
+ const header = error instanceof BladeApiError ? error.response.headers.get("Retry-After")?.trim() : void 0;
5943
+ const retryAfter = header ? /^\d+$/.test(header) ? Number(header) * 1e3 : Date.parse(header) - Date.now() : 0;
5944
+ const delay = Math.max(backoff, Number.isNaN(retryAfter) ? 0 : retryAfter);
5945
+ this.nextAt = delay > MAX_TIMER_MS ? false : Date.now() + delay;
5946
+ }
5947
+ /** Zero means ready, false means stop, otherwise wait at least this many ms. */
5948
+ waitMs() {
5949
+ return this.nextAt === false ? false : Math.max(0, this.nextAt - Date.now());
5950
+ }
5951
+ };
5952
+
5845
5953
  // src/platform-endpoints.ts
5846
5954
  var PLATFORM_SERVICE_NAMES = [
5847
5955
  "os",
@@ -5924,7 +6032,7 @@ function resolveServiceUrl(endpoints, name, path = "") {
5924
6032
 
5925
6033
  // src/version.ts
5926
6034
  var SDK_NAME = "agent-client";
5927
- var SDK_VERSION = true ? "2610.0.0-beta.47" : "1.1.1";
6035
+ var SDK_VERSION = true ? "2610.0.0-beta.48" : "1.1.1";
5928
6036
 
5929
6037
  // src/commands/protocol.ts
5930
6038
  function isCommandEnvelope(value) {
@@ -6026,6 +6134,7 @@ var Task = type2({
6026
6134
  });
6027
6135
  export {
6028
6136
  AgentSession,
6137
+ AuthBusyReconnect,
6029
6138
  BladeApiError,
6030
6139
  BladeClient,
6031
6140
  ChatProjectsResource,
@@ -6035,6 +6144,7 @@ export {
6035
6144
  EMPTY_PLATFORM_ENDPOINTS,
6036
6145
  LayoutType,
6037
6146
  ModelsResource,
6147
+ PollingBackoff,
6038
6148
  SDK_NAME,
6039
6149
  SDK_VERSION,
6040
6150
  SessionHub,
@@ -6045,6 +6155,7 @@ export {
6045
6155
  TaskStatus,
6046
6156
  acceptedPostChatFollowupCompletesLatestRun,
6047
6157
  acknowledgeTurnEvents,
6158
+ authBusyRetryDelayMs,
6048
6159
  buildMessageContent,
6049
6160
  canToggleComputer,
6050
6161
  chatErrorForDisplay,