@blade-hq/agent-client 2610.0.0-beta.0 → 2610.0.0-beta.10

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
@@ -56,6 +56,7 @@ new BladeClient({
56
56
  baseUrl: "https://blade.example.com", // 后端地址;同域部署可传 ""
57
57
  token: "sk-blade-xxx", // 可选:PAT。不传则用 cookie 或 login()
58
58
  tokenStorage: "local", // 可选:login() 的令牌存哪("local" 默认 / "memory")
59
+ streamTokens: false, // 可选:只接收完成态内容,不订阅逐 token 增量
59
60
  })
60
61
  ```
61
62
 
@@ -133,6 +134,7 @@ Solution 和 Skill 都声明为 `{ path, content }[]` 文本文件;SDK 负责
133
134
 
134
135
  ```ts
135
136
  await client.sessions.listSessions() // 会话列表
137
+ await client.sessions.searchOwnedContent({ q: "项目报告" }) // 搜索自己的对话和文件名
136
138
  await client.sessions.getSession(id) // 会话详情
137
139
  await client.sessions.updateSession(id, { intent: "新标题" })
138
140
  await client.sessions.deleteSession(id)
@@ -224,6 +226,18 @@ await chat.countFiles() // 递归统计,不含目录
224
226
  await chat.downloadFile("输出/简历.md", "简历.md") // 浏览器下载
225
227
  ```
226
228
 
229
+ 对符合评价条件的最新整体结果,可读取或更新当前用户的反馈:
230
+
231
+ ```ts
232
+ const feedback = await client.sessions.listResultFeedback(id)
233
+ await client.sessions.putResultFeedback(id, assistantEntryId, {
234
+ helpful: false,
235
+ reason: "incomplete",
236
+ })
237
+ ```
238
+
239
+ `ResultFeedbackReason` 是固定枚举;Server 会校验结果资格、写权限,并自行推导 Chat / 软件工厂场景和项目 ID。
240
+
227
241
  ### 模型目录
228
242
 
229
243
  ```ts
@@ -502,11 +516,11 @@ transformSlashCommand(skillId, prompt, { local: false, installed: false })
502
516
  - **SDK 身份**:`SDK_NAME`、`SDK_VERSION`
503
517
  - **声明式会话**:`SessionDefinition`、`SolutionDefinition`、`SkillDefinition`、`SessionConfig`、`TextFile`、`SessionSetupError`、`SessionSetupStage`
504
518
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
505
- - **会话资源(REST)**:`SessionsResource`、`CreateSessionRequest`、`ImportSessionOptions`、`AppCliDefinition`、`AppCliAttachment`、`AttachAppOptions`、`PaginatedSessionsResult`、`SessionHistory`、`SessionContextStats`、`ShareLinkResult`、`FileEntry`、`UploadFileEntry`、`UploadFilesOptions`、`SessionProfile`、`SessionDetail`、`SessionInfo`、`SessionStatus`、`SessionPortMapping`、`ModeId`、`TemplateId`、`PrimarySkillSnapshot`、`PrimarySkillParallelMode`
519
+ - **会话资源(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`
506
520
  - **会话回放**:`ReplayState`、`ReplaySpeed`、`ReplayPreview`、`ReplaySnapshot`、`toReplaySnapshot`、`DEFAULT_REPLAY_SPEED`
507
521
  - **会话状态机**:`SessionHub`、`SessionState`、`SendOptions`、`ConnectionStatus`、`AskUserAnswerData`、`AgentLoopInfo`、`ActiveCompactionState`、`createInitialSessionState`、`AgentSessionEventName`
508
522
  - **页面协作**:`EmbeddedChat`、`EmbeddedChatOptions`、`CommandHandler`、`CommandEnvelope`、`InboundAction`、`InboundEnvelope`、`isCommandEnvelope`、`isInboundEnvelope`
509
- - **消息与投影协议**:`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`ToolBridgeContent`、`CompactionInfo`、`MemoryRefInfo`、`ArchivedFileInfo`、`ArchivedToolCallInfo`、`TurnProjection`、`ContentBlock`、`PatchEnvelope`、`MemoryRef`、`buildMessageContent`、`normalizeMessageContent`、`isHiddenInternalMessage`、`transformSlashCommand`、`SkillMentionAvailability`、`extractTextAttachments`、`ParsedTextAttachment`、`ParsedTextContext`
523
+ - **消息与投影协议**:`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`ToolBridgeContent`、`CompactionInfo`、`MemoryRefInfo`、`ArchivedFileInfo`、`ArchivedToolCallInfo`、`TurnProjection`、`ContentBlock`、`PatchEnvelope`、`MemoryRef`、`PostChatFollowup`、`FinalArtifact`、`latestPostChatFollowup`、`buildMessageContent`、`normalizeMessageContent`、`isHiddenInternalMessage`、`transformSlashCommand`、`SkillMentionAvailability`、`extractTextAttachments`、`ParsedTextAttachment`、`ParsedTextContext`
510
524
  - **Solution / 任务协议**:`Solution`、`SolutionAppField`、`SolutionAppState`、`SolutionAppUiConfig`、`SolutionRef`、`PublishedSolutionRef`、`ExistingSolutionRef`、`PreparedSolution`、`PreparedSolutionAsset`、`LayoutType`、`BizRole`、`TaskStatus`、`BackgroundTask`、`BackgroundTaskStopResult`
511
525
  - **Headless**:`HeadlessResource`、`RunOptions`、`RunResult`、`RunTrace`
512
- - **低层通道(apps/web 等高级集成)**:`createSocket`、`CreateSocketOptions`、`TypedSocket`、`AsrAudioPayload`、`ClientProjectionBuilder`、`RawEvent`
526
+ - **低层通道(apps/web 等高级集成)**:`createSocket`、`CreateSocketOptions`、`TypedSocket`、`AsrAudioPayload`、`ClientProjectionBuilder`、`RawEvent`、`acceptedPostChatFollowupCompletesLatestRun`、`reconcileOptimisticUserTurns`
@@ -23,6 +23,8 @@ export interface BladeClientOptions {
23
23
  tokenStorage?: TokenStorageMode;
24
24
  fetchImpl?: typeof fetch;
25
25
  onRefreshSuccess?: () => void | Promise<void>;
26
+ /** 是否订阅逐 token 增量,默认 true。关闭后仍接收完整消息和语义事件。 */
27
+ streamTokens?: boolean;
26
28
  }
27
29
  export declare class BladeClient {
28
30
  private refreshPromise;
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export { SessionHub } from "./session/hub";
9
9
  export type { AgentSessionEvents, AgentSessionEventName } from "./session/events";
10
10
  export { SessionSetupError } from "./session/definition";
11
11
  export type { SessionConfig, SessionDefinition, SessionSetupStage, SkillDefinition, TextFile, SolutionDefinition, } from "./session/definition";
12
- export { createInitialSessionState, toReplaySnapshot } from "./session/state";
12
+ export { createInitialSessionState, reconcileOptimisticUserTurns, toReplaySnapshot, } from "./session/state";
13
13
  export type { AskUserAnswerData, AgentLoopInfo, ActiveCompactionState, ConnectionStatus, ReplaySnapshot, SessionState, } from "./session/state";
14
14
  export { connectEmbedded } from "./commands/embedded";
15
15
  export type { EmbeddedChat, EmbeddedChatOptions } from "./commands/embedded";
@@ -21,11 +21,12 @@ export type { HeadlessResource } from "./resources/headless";
21
21
  export { ModelsResource } from "./resources/models";
22
22
  export type { ModelCatalog, ModelOption } from "./resources/models";
23
23
  export type { SessionsResource } from "./resources/sessions";
24
- export type { CreateSessionRequest, AppCliAttachment, AppCliDefinition, FileEntry, ImportSessionOptions, PaginatedSessionsResult, SessionContextStats, SessionHistory, ShareLinkResult, UploadFileEntry, UploadFilesOptions, } from "./resources/sessions";
24
+ export type { CreateSessionRequest, AppCliAttachment, AppCliDefinition, FileEntry, ImportSessionOptions, PaginatedSessionsResult, GlobalSearchConversationResult, GlobalSearchFileResult, GlobalSearchResult, GlobalSearchResultItem, ResultFeedback, ResultFeedbackReason, SessionContextStats, SessionHistory, ShareLinkResult, UploadFileEntry, UploadFilesOptions, } from "./resources/sessions";
25
25
  export type { ArchivedFileInfo, ArchivedToolCallInfo, ChatMessage, CompactionInfo, FileContentPart, ImageUrlContentPart, MemoryRefInfo, MessageContent, MessageContentPart, TextContentPart, ToolBridgeContent, ToolCallInfo, } from "./schemas/message";
26
26
  export { buildMessageContent, contentPreview, extractTextAttachments, getFileParts, getImageParts, getTextContent, groupMessagesByLoop, isHiddenInternalMessage, normalizeMessageContent, transformSlashCommand, } from "./schemas/message-utils";
27
27
  export type { ParsedTextAttachment, ParsedTextContext, SkillMentionAvailability, } from "./schemas/message-utils";
28
- export type { ContentBlock, MemoryRef, PatchEnvelope, TurnProjection } from "./schemas/projection";
28
+ export type { ContentBlock, FinalArtifact, MemoryRef, PatchEnvelope, PostChatFollowup, TurnProjection, } from "./schemas/projection";
29
+ export { latestPostChatFollowup } from "./schemas/projection-utils";
29
30
  export { DEFAULT_REPLAY_SPEED } from "./schemas/session";
30
31
  export { SessionInfo, SessionStatus } from "./schemas/session";
31
32
  export type { ModeId, PrimarySkillParallelMode, PrimarySkillSnapshot, ReplayPreview, ReplaySpeed, ReplayState, SessionDetail, SessionPortMapping, PublishedSolutionRef, TemplateId, } from "./schemas/session";
@@ -33,7 +34,7 @@ export { LayoutType } from "./schemas/solution";
33
34
  export type { BizRole, Solution, SolutionAppField, SolutionAppState, SolutionAppUiConfig, } from "./schemas/solution";
34
35
  export { Task, TaskStatus } from "./schemas/task";
35
36
  export type { BackgroundTask, BackgroundTaskStopResult } from "./schemas/background";
36
- export { ClientProjectionBuilder } from "./shared/projection";
37
+ export { acceptedPostChatFollowupCompletesLatestRun, ClientProjectionBuilder } from "./shared/projection";
37
38
  export type { RawEvent } from "./shared/projection";
38
39
  export { createSocket } from "./socket";
39
40
  export type { CreateSocketOptions, TypedSocket } from "./socket";
package/dist/index.js CHANGED
@@ -350,7 +350,11 @@ var HeadlessResource = class {
350
350
  socket.on("chat:start", onChatStart);
351
351
  socket.on("chat:end", onChatEnd);
352
352
  socket.on("system:error", onSystemError);
353
- socket.emit("session:subscribe", { session_id, event_format: "raw" });
353
+ socket.emit("session:subscribe", {
354
+ session_id,
355
+ event_format: "raw",
356
+ stream_tokens: this.client.options.streamTokens !== false
357
+ });
354
358
  socket.emit(
355
359
  "chat:send",
356
360
  {
@@ -893,7 +897,7 @@ function sourceLoopFor(loopId, loopDescriptions) {
893
897
  if (!description) return null;
894
898
  return { loop_name: loopId, description };
895
899
  }
896
- var MAX_FINAL_ARTIFACTS = 3;
900
+ var MAX_FINAL_ARTIFACTS = 20;
897
901
  function normalizeFinalArtifacts(raw) {
898
902
  if (!Array.isArray(raw)) return [];
899
903
  const artifacts = [];
@@ -927,12 +931,16 @@ function normalizePostChatFollowup(data, assistantEntryId) {
927
931
  }
928
932
  const recaption = String(data.recaption ?? "");
929
933
  const finalArtifacts = normalizeFinalArtifacts(data.final_artifacts);
930
- if (suggestions.length === 0 && !recaption && finalArtifacts.length === 0) return null;
934
+ const feedbackEligible = data.feedback_eligible === true;
935
+ if (suggestions.length === 0 && !recaption && finalArtifacts.length === 0 && !feedbackEligible)
936
+ return null;
931
937
  return {
932
938
  assistant_entry_id: assistantEntryId,
933
939
  recaption,
934
940
  suggestions: suggestions.slice(0, 3),
935
- final_artifacts: finalArtifacts
941
+ final_artifacts: finalArtifacts,
942
+ feedback_eligible: feedbackEligible,
943
+ feedback_scope: "latest_result"
936
944
  };
937
945
  }
938
946
  function normalizeChildPause(pausePayload) {
@@ -1016,11 +1024,60 @@ function toolStatusFromResult(result) {
1016
1024
  }
1017
1025
 
1018
1026
  // src/shared/projection/builder.ts
1027
+ function hasChatRunEvent(events) {
1028
+ return events.some(
1029
+ (event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
1030
+ );
1031
+ }
1032
+ function acceptedPostChatFollowupCompletesLatestRun(events, updates) {
1033
+ const acceptedAssistantEntryIds = new Set(
1034
+ updates.flatMap((update) => {
1035
+ if (update.kind !== "upsert") return [];
1036
+ const followup = update.turn.post_chat_followup;
1037
+ return followup?.assistant_entry_id ? [followup.assistant_entry_id] : [];
1038
+ })
1039
+ );
1040
+ for (let index = events.length - 1; index >= 0; index -= 1) {
1041
+ const event = events[index];
1042
+ if (event?.type !== "session:post_chat_followup") continue;
1043
+ const assistantEntryId = String(event.payload.assistant_entry_id ?? "");
1044
+ return acceptedAssistantEntryIds.has(assistantEntryId) && !hasChatRunEvent(events.slice(index + 1));
1045
+ }
1046
+ return false;
1047
+ }
1019
1048
  function isConversationTurn(turn) {
1020
1049
  return (turn.role === "user" || turn.role === "assistant") && (turn.kind ?? "message") === "message" && !turn.blocks.some(
1021
1050
  (block) => block.type === "mode_change" || block.type === "plan_status" || block.type === "ask_user_answer"
1022
1051
  );
1023
1052
  }
1053
+ function completedResponseText(content) {
1054
+ if (typeof content === "string") return content;
1055
+ if (!Array.isArray(content)) return content == null ? "" : String(content);
1056
+ return content.filter(
1057
+ (part) => typeof part === "object" && part !== null && !Array.isArray(part)
1058
+ ).filter((part) => part.type === "text").map((part) => String(part.text ?? "")).join("");
1059
+ }
1060
+ function replaceTextContent(state, content) {
1061
+ if (state.blocks.some((block) => block.type === "text")) return;
1062
+ const replacement = { type: "text", content };
1063
+ const blocks = [];
1064
+ let inserted = false;
1065
+ for (const block of state.blocks) {
1066
+ if (block.type === "text") {
1067
+ if (content && !inserted) {
1068
+ blocks.push(replacement);
1069
+ inserted = true;
1070
+ }
1071
+ continue;
1072
+ }
1073
+ blocks.push(block);
1074
+ }
1075
+ if (content && !inserted) {
1076
+ const toolIndex = blocks.findIndex((block) => block.type === "tool_use");
1077
+ blocks.splice(toolIndex < 0 ? blocks.length : toolIndex, 0, replacement);
1078
+ }
1079
+ state.blocks = blocks;
1080
+ }
1024
1081
  var ClientProjectionBuilder = class {
1025
1082
  activeTurns = /* @__PURE__ */ new Map();
1026
1083
  lastTurnIds = /* @__PURE__ */ new Map();
@@ -1103,6 +1160,9 @@ var ClientProjectionBuilder = class {
1103
1160
  return this.onDelta(state, "thinking", "", String(payload.content ?? ""));
1104
1161
  case "llm:content:delta":
1105
1162
  return this.onDelta(state, "text", "", String(payload.content ?? ""));
1163
+ case "llm:content:complete":
1164
+ replaceTextContent(state, String(payload.content ?? ""));
1165
+ return this.syncPatch(state);
1106
1166
  case "llm:tool_call:created":
1107
1167
  return this.onToolCallCreated(state, payload);
1108
1168
  case "llm:response:done":
@@ -1392,6 +1452,9 @@ var ClientProjectionBuilder = class {
1392
1452
  if (typeof payload.usage === "object" && payload.usage !== null) {
1393
1453
  state.usage = { ...payload.usage };
1394
1454
  }
1455
+ if ("content" in payload) {
1456
+ replaceTextContent(state, completedResponseText(payload.content));
1457
+ }
1395
1458
  const toolCalls = payload.tool_calls;
1396
1459
  if (Array.isArray(toolCalls)) {
1397
1460
  for (const raw of toolCalls) {
@@ -2195,6 +2258,11 @@ var SessionsResource = class {
2195
2258
  listSessionsWithSkillData() {
2196
2259
  return this.client.json("GET", "/api/sessions/with-skill-data");
2197
2260
  }
2261
+ searchOwnedContent(params) {
2262
+ const searchParams = new URLSearchParams({ q: params.q.trim() });
2263
+ if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
2264
+ return this.client.json("GET", `/api/search?${searchParams.toString()}`);
2265
+ }
2198
2266
  createSession(intent, template_id, primary_skill_id) {
2199
2267
  return this.createSessionWithRequest({ intent, template_id, primary_skill_id });
2200
2268
  }
@@ -2261,6 +2329,16 @@ var SessionsResource = class {
2261
2329
  );
2262
2330
  return projectHistory(entries);
2263
2331
  }
2332
+ listResultFeedback(sessionId) {
2333
+ return this.client.json("GET", `/api/sessions/${encodeURIComponent(sessionId)}/feedback`);
2334
+ }
2335
+ putResultFeedback(sessionId, assistantEntryId, feedback) {
2336
+ return this.client.json(
2337
+ "PUT",
2338
+ `/api/sessions/${encodeURIComponent(sessionId)}/turns/${encodeURIComponent(assistantEntryId)}/feedback`,
2339
+ feedback
2340
+ );
2341
+ }
2264
2342
  getSessionContextStats(sessionId, init) {
2265
2343
  return this.client.jsonFromInit(`/api/sessions/${sessionId}/context-stats`, init);
2266
2344
  }
@@ -3062,6 +3140,25 @@ function orderTurns(turns) {
3062
3140
  }
3063
3141
  return turns;
3064
3142
  }
3143
+ function reconcileOptimisticUserTurns(currentTurns, incomingTurns) {
3144
+ const currentById = new Map(currentTurns.map((turn) => [turn.turn_id, turn]));
3145
+ const optimisticUserTurn = currentTurns.find(
3146
+ (turn) => turn.role === "user" && turn.turn_id.startsWith("local-user-")
3147
+ );
3148
+ let optimisticUserMatched = false;
3149
+ return incomingTurns.map((turn) => {
3150
+ const current = currentById.get(turn.turn_id);
3151
+ if (current) {
3152
+ const currentRenderId = current.client_render_id;
3153
+ return turn.client_render_id || !currentRenderId ? turn : { ...turn, client_render_id: currentRenderId };
3154
+ }
3155
+ if (!optimisticUserMatched && optimisticUserTurn && turn.role === "user" && !turn.turn_id.startsWith("local-user-")) {
3156
+ optimisticUserMatched = true;
3157
+ return { ...turn, client_render_id: optimisticUserTurn.turn_id };
3158
+ }
3159
+ return turn;
3160
+ });
3161
+ }
3065
3162
  function areAgentLoopsEqual(left, right) {
3066
3163
  if (!left) return Object.keys(right).length === 0;
3067
3164
  const leftEntries = Object.entries(left);
@@ -3073,7 +3170,7 @@ function areAgentLoopsEqual(left, right) {
3073
3170
  });
3074
3171
  }
3075
3172
  function withTurns(state, turns) {
3076
- const orderedTurns = orderTurns(turns);
3173
+ const orderedTurns = orderTurns(reconcileOptimisticUserTurns(state.turns, turns));
3077
3174
  const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
3078
3175
  const isWaitingForInput = orderedTurns.some(
3079
3176
  (turn) => turn.tool_calls.some((toolCall) => toolCall.status === "awaiting_answer")
@@ -3083,7 +3180,13 @@ function withTurns(state, turns) {
3083
3180
  const preservedErrors = lastTurnId ? state.messages.filter(
3084
3181
  (m) => m.role === "error" && typeof m.entry_id === "string" && m.entry_id.startsWith(`${ERROR_ANCHOR_PREFIX}${lastTurnId}:`)
3085
3182
  ) : [];
3086
- const mergedMessages = preservedErrors.length > 0 ? [...messages, ...preservedErrors] : messages;
3183
+ const turnById = new Map(orderedTurns.map((turn) => [turn.turn_id, turn]));
3184
+ const messagesWithRenderIds = messages.map((message) => {
3185
+ if (!message.entry_id) return message;
3186
+ const renderId = turnById.get(message.entry_id)?.client_render_id;
3187
+ return renderId ? { ...message, render_id: renderId } : message;
3188
+ });
3189
+ const mergedMessages = preservedErrors.length > 0 ? [...messagesWithRenderIds, ...preservedErrors] : messagesWithRenderIds;
3087
3190
  const latestMode = [...orderedTurns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
3088
3191
  return {
3089
3192
  ...state,
@@ -3118,14 +3221,17 @@ function addUserMessage(state, content) {
3118
3221
  }
3119
3222
  function upsertTurn(state, turn) {
3120
3223
  let existing = [...state.turns];
3121
- if (turn.role === "user" && !turn.turn_id.startsWith("local-user-")) {
3122
- existing = existing.filter((t) => !t.turn_id.startsWith("local-user-"));
3224
+ const [nextTurn] = reconcileOptimisticUserTurns(existing, [turn]);
3225
+ if (nextTurn.client_render_id && turn.role === "user") {
3226
+ existing = existing.filter(
3227
+ (item) => !(item.role === "user" && item.turn_id.startsWith("local-user-"))
3228
+ );
3123
3229
  }
3124
- const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
3230
+ const index = existing.findIndex((item) => item.turn_id === nextTurn.turn_id);
3125
3231
  if (index >= 0) {
3126
- existing[index] = turn;
3232
+ existing[index] = nextTurn;
3127
3233
  } else {
3128
- existing.push(turn);
3234
+ existing.push(nextTurn);
3129
3235
  }
3130
3236
  return withTurns(state, existing);
3131
3237
  }
@@ -3229,7 +3335,7 @@ function extractTerminalChatEndStatus(events) {
3229
3335
  }
3230
3336
  return null;
3231
3337
  }
3232
- function hasChatRunEvent(events) {
3338
+ function hasChatRunEvent2(events) {
3233
3339
  return events.some(
3234
3340
  (event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
3235
3341
  );
@@ -3281,6 +3387,7 @@ var AgentSession = class _AgentSession {
3281
3387
  const session = new _AgentSession(
3282
3388
  sessionId,
3283
3389
  {
3390
+ streamTokens: false,
3284
3391
  emitEvent: readonlyError,
3285
3392
  emitWithAck: async () => readonlyError(),
3286
3393
  ensureConnected: readonlyError,
@@ -3654,7 +3761,7 @@ var AgentSession = class _AgentSession {
3654
3761
  _handleTurnEvents(events) {
3655
3762
  if (!events.length) return;
3656
3763
  const terminalChatEndStatus = extractTerminalChatEndStatus(events);
3657
- if (!terminalChatEndStatus && hasChatRunEvent(events)) {
3764
+ if (!terminalChatEndStatus && hasChatRunEvent2(events)) {
3658
3765
  this.markRunningIfIdle();
3659
3766
  }
3660
3767
  if (this.state.turns.length) {
@@ -3668,8 +3775,8 @@ var AgentSession = class _AgentSession {
3668
3775
  this.emitter.emit("workspaceChanged", {});
3669
3776
  }
3670
3777
  }
3671
- if (terminalChatEndStatus) {
3672
- this.finishStreaming(terminalChatEndStatus);
3778
+ if (terminalChatEndStatus || acceptedPostChatFollowupCompletesLatestRun(events, updates)) {
3779
+ this.completeChat(terminalChatEndStatus ?? "completed");
3673
3780
  }
3674
3781
  }
3675
3782
  /** @internal */
@@ -3679,10 +3786,7 @@ var AgentSession = class _AgentSession {
3679
3786
  /** @internal */
3680
3787
  _handleChatEnd(status) {
3681
3788
  this.flushPatches();
3682
- this.finishStreaming(status);
3683
- this.recentChatEndAt = Date.now();
3684
- void this.syncTurnsFromHistory();
3685
- this.emitter.emit("chatEnd", { status });
3789
+ this.completeChat(status);
3686
3790
  }
3687
3791
  /** @internal */
3688
3792
  _handleSystemError(message) {
@@ -3826,6 +3930,12 @@ var AgentSession = class _AgentSession {
3826
3930
  return next;
3827
3931
  });
3828
3932
  }
3933
+ completeChat(status) {
3934
+ this.finishStreaming(status);
3935
+ this.recentChatEndAt = Date.now();
3936
+ void this.syncTurnsFromHistory();
3937
+ this.emitter.emit("chatEnd", { status });
3938
+ }
3829
3939
  applyTurn(turn) {
3830
3940
  this.flushPatches();
3831
3941
  const previous = this.state;
@@ -4008,7 +4118,12 @@ var AgentSession = class _AgentSession {
4008
4118
  if (this.joinPending) return this.joinPending;
4009
4119
  const join = this.runtime.emitWithAck(
4010
4120
  "session:subscribe",
4011
- { session_id: this.sessionId, event_format: "raw", resume: true },
4121
+ {
4122
+ session_id: this.sessionId,
4123
+ event_format: "raw",
4124
+ resume: true,
4125
+ stream_tokens: this.runtime.streamTokens
4126
+ },
4012
4127
  5e3
4013
4128
  ).then((response) => {
4014
4129
  if (response && response.ok === false) {
@@ -4144,6 +4259,7 @@ var SessionHub = class {
4144
4259
  }
4145
4260
  runtimeFor() {
4146
4261
  return {
4262
+ streamTokens: this.client.options?.streamTokens !== false,
4147
4263
  emitEvent: (event, data) => {
4148
4264
  ;
4149
4265
  this.client.socket().emit(event, data);
@@ -4383,7 +4499,7 @@ function resolveAuthToken(options) {
4383
4499
 
4384
4500
  // src/version.ts
4385
4501
  var SDK_NAME = "agent-client";
4386
- var SDK_VERSION = true ? "2610.0.0-beta.0" : "1.1.1";
4502
+ var SDK_VERSION = true ? "2610.0.0-beta.10" : "1.1.1";
4387
4503
 
4388
4504
  // src/socket.ts
4389
4505
  function withSdkIdentity(auth) {
@@ -4835,6 +4951,19 @@ function connectEmbedded(options) {
4835
4951
  };
4836
4952
  }
4837
4953
 
4954
+ // src/schemas/projection-utils.ts
4955
+ function latestPostChatFollowup(turns) {
4956
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
4957
+ const turn = turns[index];
4958
+ if (turn.loop_id === "root" && (turn.role === "user" || turn.role === "assistant") && (turn.kind ?? "message") === "message" && !turn.blocks.some(
4959
+ (block) => ["mode_change", "plan_status", "ask_user_answer"].includes(block.type)
4960
+ )) {
4961
+ return turn.role === "assistant" ? turn.post_chat_followup ?? null : null;
4962
+ }
4963
+ }
4964
+ return null;
4965
+ }
4966
+
4838
4967
  // src/schemas/solution.ts
4839
4968
  var LayoutType = /* @__PURE__ */ ((LayoutType2) => {
4840
4969
  LayoutType2["Default"] = "default";
@@ -4877,6 +5006,7 @@ export {
4877
5006
  SessionStatus,
4878
5007
  Task,
4879
5008
  TaskStatus,
5009
+ acceptedPostChatFollowupCompletesLatestRun,
4880
5010
  buildMessageContent,
4881
5011
  connectEmbedded,
4882
5012
  contentPreview,
@@ -4890,7 +5020,9 @@ export {
4890
5020
  isCommandEnvelope,
4891
5021
  isHiddenInternalMessage,
4892
5022
  isInboundEnvelope,
5023
+ latestPostChatFollowup,
4893
5024
  normalizeMessageContent,
5025
+ reconcileOptimisticUserTurns,
4894
5026
  toReplaySnapshot,
4895
5027
  transformSlashCommand
4896
5028
  };