@blade-hq/agent-client 2610.0.0-beta.31 → 2610.0.0-beta.32

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
@@ -431,7 +431,7 @@ chat.on("chatEnd", (e) => console.log("回复结束", e.status))
431
431
  chat.on("error", (e) => console.error(e.message))
432
432
  ```
433
433
 
434
- 完整事件表见 `AgentSessionEvents` 类型定义(含 `modeChange` / `workspaceChanged` / `artifact` / `notification` / `backgroundTask` / `taskListUpdated` / `rewind` / `replayMismatch` 等)。`on()` 返回取消函数;handler 抛异常只告警,不影响会话。
434
+ 完整事件表见 `AgentSessionEvents` 类型定义(含 `modeChange` / `workspaceChanged` / `artifact` / `notification` / `backgroundTask` / `taskListUpdated` / `rewind` / `replayMismatch` 等)。`toolResult.source` 区分实时结果、首次连接回放和断线重连回放;`on()` 返回取消函数,handler 抛异常只告警、不影响会话。
435
435
 
436
436
  ## iframe 嵌入形态:connectEmbedded
437
437
 
@@ -583,7 +583,7 @@ transformSlashCommand(skillId, prompt, { local: false, installed: false })
583
583
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
584
584
  - **会话资源(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`
585
585
  - **会话回放**:`ReplayState`、`ReplaySpeed`、`ReplayPreview`、`ReplaySnapshot`、`toReplaySnapshot`、`DEFAULT_REPLAY_SPEED`
586
- - **会话状态机**:`SessionHub`、`SessionState`、`SendOptions`、`ConnectionStatus`、`AskUserAnswerData`、`AgentLoopInfo`、`ActiveCompactionState`、`createInitialSessionState`、`AgentSessionEventName`
586
+ - **会话状态机**:`SessionHub`、`SessionConnectOptions`、`SessionState`、`SendOptions`、`ConnectionStatus`、`AskUserAnswerData`、`AgentLoopInfo`、`ActiveCompactionState`、`createInitialSessionState`、`AgentSessionEventName`
587
587
  - **页面协作**:`EmbeddedChat`、`EmbeddedChatOptions`、`CommandHandler`、`CommandEnvelope`、`InboundAction`、`InboundEnvelope`、`isCommandEnvelope`、`isInboundEnvelope`
588
588
  - **消息与投影协议**:`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`
589
589
  - **Solution / 任务协议**:`Solution`、`SolutionAppField`、`SolutionAppState`、`SolutionAppUiConfig`、`SolutionRef`、`PublishedSolutionRef`、`ExistingSolutionRef`、`PreparedSolution`、`PreparedSolutionAsset`、`LayoutType`、`BizRole`、`TaskStatus`、`BackgroundTask`、`BackgroundTaskStopResult`
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export { SDK_NAME, SDK_VERSION } from "./version";
8
8
  export { AgentSession } from "./session/agent-session";
9
9
  export type { AttachAppOptions, SendOptions } from "./session/agent-session";
10
10
  export { SessionHub } from "./session/hub";
11
+ export type { SessionConnectOptions } from "./session/hub";
11
12
  export type { AgentSessionEvents, AgentSessionEventName } from "./session/events";
12
13
  export { SessionSetupError } from "./session/definition";
13
14
  export type { SessionConfig, SessionDefinition, SessionSetupStage, SkillDefinition, TextFile, SolutionDefinition, } from "./session/definition";
package/dist/index.js CHANGED
@@ -3712,6 +3712,10 @@ var AgentSession = class _AgentSession {
3712
3712
  patchFlushCancel = null;
3713
3713
  joined = false;
3714
3714
  joinPending = null;
3715
+ // bootstrap 与每次 reconnect 共用一条订阅生命周期队列。它们都会重置 join
3716
+ // 状态并临时设置 replay source,禁止并发,否则较早操作的 finally 会清掉
3717
+ // 较晚操作仍在使用的来源上下文。
3718
+ subscriptionQueue = Promise.resolve();
3715
3719
  // 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
3716
3720
  // 请求响应可能乱序回来,晚到的旧值会把已经退出的会话又显示成回放中;用
3717
3721
  // 序号丢弃过期响应又会在失败时留下中间态。串行执行就没有这些情况:每个
@@ -3721,6 +3725,7 @@ var AgentSession = class _AgentSession {
3721
3725
  pendingReplayMode;
3722
3726
  pendingReplayKbIds;
3723
3727
  recentChatEndAt = 0;
3728
+ subscriptionReplaySource = null;
3724
3729
  disposed = false;
3725
3730
  getAppContext = null;
3726
3731
  sendPending = false;
@@ -4038,27 +4043,47 @@ var AgentSession = class _AgentSession {
4038
4043
  this.update((s) => ({ ...s, connection }));
4039
4044
  }
4040
4045
  /** @internal socket 重连后由 Hub 调用:重新加入房间并补数。 */
4041
- async _rejoinAfterReconnect() {
4046
+ _rejoinAfterReconnect() {
4042
4047
  const turnsBeforeRejoin = this.state.turns;
4043
- this.joined = false;
4044
- this.joinPending = null;
4045
- try {
4046
- await this.ensureJoined();
4047
- await Promise.all([
4048
- this.syncTurnsFromHistory({
4049
- clearOnEmpty: true,
4050
- preserveChangesSince: turnsBeforeRejoin
4051
- }),
4052
- this.syncReplayFromSession()
4053
- ]);
4054
- } catch {
4055
- }
4048
+ return this.enqueueSubscription(async () => {
4049
+ this.joined = false;
4050
+ this.joinPending = null;
4051
+ try {
4052
+ this.subscriptionReplaySource = "reconnect_replay";
4053
+ try {
4054
+ await this.ensureJoined();
4055
+ } finally {
4056
+ this.subscriptionReplaySource = null;
4057
+ }
4058
+ await Promise.all([
4059
+ this.syncTurnsFromHistory({
4060
+ clearOnEmpty: true,
4061
+ preserveChangesSince: turnsBeforeRejoin
4062
+ }),
4063
+ this.syncReplayFromSession()
4064
+ ]);
4065
+ } catch {
4066
+ }
4067
+ });
4056
4068
  }
4057
4069
  /** @internal 首次连接:加载历史 + 加入房间。 */
4058
- async _bootstrap() {
4059
- await this.syncTurnsFromHistory({ suppressErrors: false });
4060
- await this.ensureJoined();
4061
- await this.syncReplayFromSession();
4070
+ _bootstrap() {
4071
+ return this.enqueueSubscription(async () => {
4072
+ await this.syncTurnsFromHistory({ suppressErrors: false });
4073
+ this.subscriptionReplaySource = "bootstrap_replay";
4074
+ try {
4075
+ await this.ensureJoined();
4076
+ } finally {
4077
+ this.subscriptionReplaySource = null;
4078
+ }
4079
+ await this.syncReplayFromSession();
4080
+ });
4081
+ }
4082
+ /** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
4083
+ enqueueSubscription(operation) {
4084
+ const run = this.subscriptionQueue.then(operation);
4085
+ this.subscriptionQueue = run.catch(() => void 0);
4086
+ return run;
4062
4087
  }
4063
4088
  /** 改回放是 owner 专属(服务端 PATCH 走 _get_owned_session)。 */
4064
4089
  get canControlReplay() {
@@ -4115,7 +4140,7 @@ var AgentSession = class _AgentSession {
4115
4140
  this.applyTurn(turn);
4116
4141
  }
4117
4142
  /** @internal 历史回放通道(event_format: raw)。 */
4118
- _handleTurnEvents(events) {
4143
+ _handleTurnEvents(events, replayed = false) {
4119
4144
  if (!events.length) return;
4120
4145
  const terminalChatEndStatus = extractTerminalChatEndStatus(events);
4121
4146
  if (!terminalChatEndStatus && hasChatRunEvent2(events) && this.state.status !== "waiting_for_input") {
@@ -4127,7 +4152,10 @@ var AgentSession = class _AgentSession {
4127
4152
  const updates = this.projectionBuilder.processBatch(events);
4128
4153
  for (const update of updates) {
4129
4154
  if (update.kind === "upsert") {
4130
- this.applyTurn(update.turn);
4155
+ this.applyTurn(
4156
+ update.turn,
4157
+ replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
4158
+ );
4131
4159
  } else if (update.kind === "workspace_changed") {
4132
4160
  this.emitter.emit("workspaceChanged", {});
4133
4161
  }
@@ -4296,11 +4324,11 @@ var AgentSession = class _AgentSession {
4296
4324
  void this.syncTurnsFromHistory();
4297
4325
  this.emitter.emit("chatEnd", { status });
4298
4326
  }
4299
- applyTurn(turn) {
4327
+ applyTurn(turn, source = "live") {
4300
4328
  this.flushPatches();
4301
4329
  const previous = this.state;
4302
4330
  this.update((s) => upsertTurn(s, turn));
4303
- this.emitTurnDerivedEvents(previous, turn);
4331
+ this.emitTurnDerivedEvents(previous, turn, source);
4304
4332
  }
4305
4333
  schedulePatchFlush() {
4306
4334
  if (this.patchFlushHandle != null) return;
@@ -4346,7 +4374,7 @@ var AgentSession = class _AgentSession {
4346
4374
  }
4347
4375
  }
4348
4376
  /** turn 数据落地后统一派生语义事件(指令 / 工具调用 / 消息 / 模式 / 预览 / OOM)。 */
4349
- emitTurnDerivedEvents(previous, turn) {
4377
+ emitTurnDerivedEvents(previous, turn, source = "live") {
4350
4378
  const mode = extractModeFromBlocks(turn.blocks);
4351
4379
  if (mode && previous.mode !== mode) {
4352
4380
  this.emitter.emit("modeChange", { mode });
@@ -4395,7 +4423,7 @@ var AgentSession = class _AgentSession {
4395
4423
  const finished = toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled";
4396
4424
  if (finished && !this.finishedToolCallIds.has(toolCall.id)) {
4397
4425
  this.finishedToolCallIds.add(toolCall.id);
4398
- this.emitter.emit("toolResult", { toolCall: info, turn });
4426
+ this.emitter.emit("toolResult", { toolCall: info, turn, source });
4399
4427
  if (isOomResult(toolCall.result)) {
4400
4428
  this.notifySandboxOom(toolCall.id);
4401
4429
  }
@@ -4437,6 +4465,7 @@ var AgentSession = class _AgentSession {
4437
4465
  try {
4438
4466
  const turns = await this.runtime.fetchTurns(this.sessionId);
4439
4467
  if (turns.length === 0 && !clearOnEmpty) return;
4468
+ if (!this.joined) this.seedToolCallDeliveryBaseline(turns);
4440
4469
  this.update((state) => {
4441
4470
  const mergeBaseline = preserveChangesSince ?? turnsAtRequestStart;
4442
4471
  if (state.turns === mergeBaseline) return withTurns(state, turns);
@@ -4455,6 +4484,17 @@ var AgentSession = class _AgentSession {
4455
4484
  if (!suppressErrors) throw error;
4456
4485
  }
4457
4486
  }
4487
+ /** 首次订阅前的 REST 历史建立语义事件基线,随后同一工具的订阅回放不再投递。 */
4488
+ seedToolCallDeliveryBaseline(turns) {
4489
+ for (const turn of turns) {
4490
+ for (const toolCall of turn.tool_calls) {
4491
+ this.seenToolCallIds.add(toolCall.id);
4492
+ if (toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled") {
4493
+ this.finishedToolCallIds.add(toolCall.id);
4494
+ }
4495
+ }
4496
+ }
4497
+ }
4458
4498
  /**
4459
4499
  * 把历史里的指令补投给宿主页面。
4460
4500
  *
@@ -4554,16 +4594,28 @@ var SessionHub = class {
4554
4594
  * 只有最后一份归还才退订房间——否则 StrictMode 双跑、路由抖动这类"连两次
4555
4595
  * 只释放一次"的场景会把仍在用的会话退订掉,之后所有 turn/chat 事件静默丢失。
4556
4596
  */
4557
- async connect(sessionId) {
4597
+ async connect(sessionId, options = {}) {
4558
4598
  const existing = this.sessions.get(sessionId);
4559
4599
  if (existing && !existing.isDisposed) {
4560
4600
  this.hold(sessionId);
4601
+ try {
4602
+ options.setup?.(existing);
4603
+ } catch (error) {
4604
+ existing.dispose();
4605
+ throw error;
4606
+ }
4561
4607
  return this.bootstrapPending.get(sessionId) ?? existing;
4562
4608
  }
4563
4609
  this.ensureBound();
4564
4610
  const session = new AgentSession(sessionId, this.runtimeFor(), this.connection);
4565
4611
  this.sessions.set(sessionId, session);
4566
4612
  this.hold(sessionId);
4613
+ try {
4614
+ options.setup?.(session);
4615
+ } catch (error) {
4616
+ session.dispose();
4617
+ throw error;
4618
+ }
4567
4619
  this.ensureConnected();
4568
4620
  const bootstrap = session._bootstrap().then(() => session);
4569
4621
  this.bootstrapPending.set(sessionId, bootstrap);
@@ -4725,9 +4777,14 @@ var SessionHub = class {
4725
4777
  socket.on("turn:end", (data) => {
4726
4778
  for (const session of this.route(data.session_id)) session._handleTurnEnd(data);
4727
4779
  });
4728
- socket.on("turn:events", (data) => {
4729
- for (const session of this.route(data.session_id)) session._handleTurnEvents(data.events ?? []);
4730
- });
4780
+ socket.on(
4781
+ "turn:events",
4782
+ (data) => {
4783
+ for (const session of this.route(data.session_id)) {
4784
+ session._handleTurnEvents(data.events ?? [], data.replayed === true);
4785
+ }
4786
+ }
4787
+ );
4731
4788
  socket.on("chat:start", (data) => {
4732
4789
  for (const session of this.route(data?.session_id)) session._handleChatStart();
4733
4790
  });
@@ -4857,7 +4914,7 @@ function resolveAuthToken(options) {
4857
4914
 
4858
4915
  // src/version.ts
4859
4916
  var SDK_NAME = "agent-client";
4860
- var SDK_VERSION = true ? "2610.0.0-beta.31" : "1.1.1";
4917
+ var SDK_VERSION = true ? "2610.0.0-beta.32" : "1.1.1";
4861
4918
 
4862
4919
  // src/socket.ts
4863
4920
  function withSdkIdentity(auth) {