@blade-hq/agent-client 2610.0.0-beta.7 → 2610.0.0-beta.71

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.
Files changed (40) hide show
  1. package/README.md +248 -9
  2. package/dist/auth.d.ts +0 -1
  3. package/dist/blade-client.d.ts +21 -3
  4. package/dist/index.d.ts +35 -6
  5. package/dist/index.js +2210 -268
  6. package/dist/index.js.map +1 -1
  7. package/dist/platform-endpoints.d.ts +14 -0
  8. package/dist/polling-backoff.d.ts +11 -0
  9. package/dist/resources/chat-groups.d.ts +26 -0
  10. package/dist/resources/computers.d.ts +115 -0
  11. package/dist/resources/models.d.ts +2 -0
  12. package/dist/resources/plugins.d.ts +43 -0
  13. package/dist/resources/sessions.d.ts +186 -1
  14. package/dist/resources/site-visits.d.ts +32 -0
  15. package/dist/rest.d.ts +8 -0
  16. package/dist/schemas/context.d.ts +49 -0
  17. package/dist/schemas/event.d.ts +9 -0
  18. package/dist/schemas/mcp-app.d.ts +30 -0
  19. package/dist/schemas/message-utils.d.ts +13 -1
  20. package/dist/schemas/message.d.ts +3 -0
  21. package/dist/schemas/projection.d.ts +37 -2
  22. package/dist/schemas/session.d.ts +9 -1
  23. package/dist/session/agent-session.d.ts +142 -16
  24. package/dist/session/events.d.ts +4 -0
  25. package/dist/session/history-page.d.ts +9 -0
  26. package/dist/session/hub.d.ts +9 -1
  27. package/dist/session/queue.d.ts +99 -0
  28. package/dist/session/state.d.ts +18 -2
  29. package/dist/shared/auth-busy.d.ts +9 -0
  30. package/dist/shared/projection/builder.d.ts +12 -0
  31. package/dist/shared/projection/context.d.ts +3 -0
  32. package/dist/shared/projection/helpers.d.ts +2 -2
  33. package/dist/shared/projection/history.d.ts +1 -0
  34. package/dist/shared/projection/index.d.ts +2 -1
  35. package/dist/shared/projection/state.d.ts +7 -0
  36. package/dist/shared/tool-ui-card.d.ts +58 -0
  37. package/dist/shared/type-guards.d.ts +2 -0
  38. package/dist/types/socket-events.d.ts +195 -6
  39. package/package.json +1 -1
  40. package/public-api.md +918 -12
@@ -5,6 +5,8 @@ export interface ToolBridgeContent {
5
5
  export interface ContentBlock {
6
6
  type: "text" | "thinking" | "tool_use" | "tool_result" | "tool_ui" | "tool_bridge" | "system_notification" | "mode_change" | "plan_status" | "ask_user_answer";
7
7
  content: unknown;
8
+ /** Stable history-entry identity for a tool_bridge delivery. */
9
+ delivery_id?: string | null;
8
10
  tool_call_id?: string | null;
9
11
  tool_name?: string | null;
10
12
  display_name?: string | null;
@@ -14,6 +16,15 @@ export interface PendingQuestionRef {
14
16
  child_tool_call_id: string;
15
17
  description?: string | null;
16
18
  }
19
+ /** 一次运行(用户轮或子智能体)的实际墙钟区间。 */
20
+ export interface RunTiming {
21
+ run_id: string;
22
+ scope: "chat" | "subagent";
23
+ loop_id: string;
24
+ started_at?: string | null;
25
+ duration_ms?: number | null;
26
+ status: "completed" | "failed" | "cancelled" | "paused" | "incomplete";
27
+ }
17
28
  export interface ToolCallProjection {
18
29
  id: string;
19
30
  tool_name: string;
@@ -22,6 +33,11 @@ export interface ToolCallProjection {
22
33
  status: "pending" | "done" | "error" | "cancelled" | "awaiting_answer" | string;
23
34
  result?: unknown;
24
35
  duration_ms?: number | null;
36
+ /** 工具真正进入执行的墙钟起点;排队/待审批的调用没有该值。 */
37
+ started_at?: string | null;
38
+ ran_in_parallel?: boolean;
39
+ /** 该工具派生的子智能体运行区间,仅作外层展示。 */
40
+ subagent_runs?: RunTiming[];
25
41
  pending_question_ref?: PendingQuestionRef | null;
26
42
  }
27
43
  export interface MemoryRef {
@@ -47,20 +63,39 @@ export interface TurnProjection {
47
63
  sequence: number;
48
64
  turn_id: string;
49
65
  loop_id: string;
50
- kind?: "message" | "compaction";
66
+ kind?: "message" | "context" | "compaction";
51
67
  role: "user" | "assistant" | "system";
52
68
  status: "streaming" | "completed" | "paused" | "failed" | "interrupted";
53
69
  blocks: ContentBlock[];
54
70
  tool_calls: ToolCallProjection[];
55
71
  model?: string | null;
56
72
  usage?: Record<string, unknown> | null;
57
- duration_ms?: number;
73
+ /** 模型请求区间(不含后续工具);未知时为 null,不用 0 冒充。 */
74
+ duration_ms?: number | null;
75
+ first_token_ms?: number | null;
76
+ stream_duration_ms?: number | null;
77
+ first_token_delivery_ms?: number | null;
58
78
  started_at?: string | null;
79
+ /** started_at 是否来自生产者上报(而非旧会话的落盘回落)。瀑布只承认前者。 */
80
+ has_reported_timing?: boolean;
59
81
  context_window?: number;
60
82
  memory_refs?: MemoryRef[] | null;
61
83
  parent_fork_tool_call_id?: string | null;
84
+ context_kind?: string | null;
85
+ context_key?: string | null;
86
+ context_name?: string | null;
87
+ context_order?: number | null;
88
+ context_action?: import("./context").ContextAction | null;
89
+ context_revision?: string | null;
90
+ /** 原始 Context 内容仅供开发者模式诊断;常规消息投影不消费。 */
91
+ context_content?: unknown;
92
+ display_title?: string | null;
93
+ display_summary?: string | null;
94
+ sources?: import("./context").ContextSourceInfo[] | null;
62
95
  /** Client-only stable identity for reconciling an optimistic user turn. */
63
96
  client_render_id?: string | null;
97
+ /** Send identity echoed by the server for exact optimistic reconciliation. */
98
+ client_request_id?: string | null;
64
99
  compaction_id?: string | null;
65
100
  summary_preview?: string | null;
66
101
  summary_full?: string | null;
@@ -82,7 +82,11 @@ export declare const SessionInfo: import("arktype/internal/variants/object.ts").
82
82
  disable_tools?: string[] | undefined;
83
83
  runtime_type?: string | null | undefined;
84
84
  daemon_id?: string | null | undefined;
85
+ agent_runtime_id?: string | null | undefined;
85
86
  workspace_path?: string | null | undefined;
87
+ ba_version?: string | null | undefined;
88
+ sandbox_version?: string | null | undefined;
89
+ chat_group_id?: string | null | undefined;
86
90
  match?: unknown;
87
91
  }, {}>;
88
92
  export type SessionInfo = Omit<typeof SessionInfo.infer, "template_id" | "solution" | "ports" | "match"> & {
@@ -106,9 +110,13 @@ export type SessionInfo = Omit<typeof SessionInfo.infer, "template_id" | "soluti
106
110
  is_example?: boolean;
107
111
  ports?: SessionPortMapping[];
108
112
  disable_tools?: string[];
109
- runtime_type?: "sandbox" | "daemon" | string | null;
113
+ runtime_type?: "sandbox" | "daemon" | "native" | string | null;
110
114
  daemon_id?: string | null;
115
+ agent_runtime_id?: string | null;
111
116
  workspace_path?: string | null;
117
+ ba_version?: string | null;
118
+ sandbox_version?: string | null;
119
+ chat_group_id?: string | null;
112
120
  /** 仅在带 q 搜索的列表响应中出现;普通列表里此字段缺省。 */
113
121
  match?: SessionSearchMatch | null;
114
122
  };
@@ -4,15 +4,17 @@ import type { PatchEnvelope, TurnProjection } from "../schemas/projection";
4
4
  import type { ReplaySpeed, ReplayState, SessionStatus } from "../schemas/session";
5
5
  import { type CommandHandler } from "../commands/registry";
6
6
  import { type AgentSessionEvents } from "./events";
7
- import type { AppCliAttachment, AppCliDefinition, FileEntry } from "../resources/sessions";
7
+ import type { AppCliAttachment, AppCliDefinition, FileEntry, HistoricalCommand, SessionTurnIndex, SessionTurnIndexItem, SessionTurnsPage, SessionTurnsQuery } from "../resources/sessions";
8
+ import { type QueueOperationResult } from "./queue";
8
9
  import { type ConnectionStatus, type SessionState } from "./state";
9
10
  /** SessionHub 注入给 AgentSession 的运行时依赖(socket 收发 + REST 补数)。 */
10
11
  export interface SessionRuntime {
11
- streamTokens: boolean;
12
12
  emitEvent(event: string, data: unknown): void;
13
13
  emitWithAck(event: string, data: unknown, timeoutMs: number): Promise<Record<string, unknown> | undefined>;
14
14
  ensureConnected(): void;
15
- fetchTurns(sessionId: string): Promise<TurnProjection[]>;
15
+ fetchTurnsPage(sessionId: string, query?: SessionTurnsQuery, init?: RequestInit): Promise<SessionTurnsPage>;
16
+ fetchTurnIndex(sessionId: string): Promise<SessionTurnIndex>;
17
+ fetchCommands(sessionId: string): Promise<HistoricalCommand[]>;
16
18
  /** 会话详情,目前只用来取回放状态。 */
17
19
  fetchSessionInfo(sessionId: string): Promise<{
18
20
  replay_state?: ReplayState | null;
@@ -53,6 +55,13 @@ export interface SendOptions {
53
55
  deprecate_entry_ids: string[];
54
56
  };
55
57
  replayDecision?: "keep_replay" | "continue_replay";
58
+ /** 用户显式选择的知识库 id;后端将元信息作为 once Context 提供给模型。 */
59
+ kbIds?: number[];
60
+ /**
61
+ * 声明"会话在跑就把这条消息放进等待队列"。默认不排队、运行中直接报错:
62
+ * 是否真的入队由服务端按运行状态判定(等待回答时发送即作答,不会被误排队)。
63
+ */
64
+ queueWhenRunning?: boolean;
56
65
  }
57
66
  export declare function sessionStatusFromChatEnd(status: string | undefined): SessionStatus;
58
67
  /**
@@ -75,10 +84,31 @@ export declare class AgentSession {
75
84
  private patchFlushCancel;
76
85
  private joined;
77
86
  private joinPending;
87
+ private subscriptionQueue;
88
+ private replaceQueue;
89
+ private timelineEpoch;
90
+ /** 定位请求的流水号:被后一次选择取代的那次定位不再提交窗口(见 locateEntry)。 */
91
+ private locateToken;
92
+ /** 在途那次定位的请求句柄。**只**服务定位:权威刷新不共用它(见 cancelPendingLocate)。 */
93
+ private locateAbort;
94
+ private pagingGeneration;
95
+ private liveRevision;
96
+ private modeRevision;
97
+ private readonly liveRevisions;
98
+ private readonly pendingOptimisticTurnIds;
99
+ private olderPending;
100
+ private replaceInFlight;
78
101
  private replayQueue;
102
+ private commandRestoreQueue;
103
+ private commandRecoveryGeneration;
104
+ private commandRestoreRetryAttempt;
105
+ private commandRestoreRetryHandle;
106
+ private bufferedCommandDeliveries;
79
107
  private pendingReplayMessage;
80
108
  private pendingReplayMode;
109
+ private pendingReplayKbIds;
81
110
  private recentChatEndAt;
111
+ private subscriptionReplaySource;
82
112
  private disposed;
83
113
  private getAppContext;
84
114
  private sendPending;
@@ -89,11 +119,55 @@ export declare class AgentSession {
89
119
  private readonly seenToolCallIds;
90
120
  private readonly finishedToolCallIds;
91
121
  private readonly emittedMessageTurnIds;
92
- constructor(sessionId: string, runtime: SessionRuntime, connection: ConnectionStatus);
122
+ constructor(sessionId: string, runtime: SessionRuntime, connection: ConnectionStatus, options?: {
123
+ staticHistory?: boolean;
124
+ });
125
+ /**
126
+ * 静态历史(分享页 / `fromTurns`):没有目录接口,也**没有**"目录还没到位"这个中间态。
127
+ *
128
+ * 宿主据此决定要不要拿已加载窗口当兜底条目——真实会话在 timeline 被重写后,窗口里还是
129
+ * 旧分支的发言,拿它兜底会把已经失效的 entry id 重新摆到轨道上,点了必然找不到。
130
+ */
131
+ readonly isStatic: boolean;
93
132
  /** 从一份静态 turn 快照构造只展示历史的会话(例如公开分享页)。 */
94
133
  static fromTurns(sessionId: string, turns: TurnProjection[]): AgentSession;
95
134
  /** 返回当前会话快照。引用稳定:状态未变化时返回同一对象。 */
96
135
  getState(): SessionState;
136
+ get hasOlderHistory(): boolean;
137
+ /** Load the page immediately preceding the current display window. */
138
+ loadOlderHistory(options?: {
139
+ beforeCommit?: () => void;
140
+ }): Promise<void>;
141
+ /**
142
+ * 定位到一条窗口外的历史发言:请服务端返回"从这条发言到最新"的窗口并整窗替换。
143
+ *
144
+ * 返回 false 表示这次定位没有落地(请求失败,或期间 timeline 被重写、会话已释放),
145
+ * 当前窗口保持不变,调用方可以原样重试。已加载的发言不要走这里——宿主直接滚动,
146
+ * 一次网络请求都不该发。
147
+ */
148
+ locateEntry(entryId: string): Promise<boolean>;
149
+ /**
150
+ * 取消在途定位:用户改选了另一条发言(哪怕那条已经加载、不需要请求),
151
+ * 先前那次定位既不该再落地,也不该继续占着替换队列——排队中的下一次定位
152
+ * 不然要等它跑完(用户看到的是"点了新目标一直转圈")。
153
+ *
154
+ * **只**中止定位自己的请求:权威刷新(运行结束 / 重连补数)不共用这个 controller,
155
+ * 否则"运行刚结束时点一下侧栏"就会把终态对账一起中止,流式与历史对不上。
156
+ */
157
+ cancelPendingLocate(): void;
158
+ /**
159
+ * timeline 被整体改写(rewind / checkout / 释放):推进纪元,并把在途定位请求一并中止
160
+ * ——它已经属于旧 timeline,却还占着替换队列,会让改写后的权威窗口排在后面等它跑完。
161
+ * 作废号仍然保留:中止只是让它更早结束。
162
+ */
163
+ private invalidateTimeline;
164
+ /**
165
+ * 拉取当前分支的用户发言目录(消息导航侧栏用)。
166
+ *
167
+ * 不在这里缓存:什么时候该刷新只有调用方知道(会话切换、历史被替换、新发言),
168
+ * 缓存只会让旧条目在 rewind 之后继续可点。
169
+ */
170
+ loadTurnIndex(): Promise<SessionTurnIndexItem[]>;
97
171
  /** 订阅状态变化。返回取消订阅函数。 */
98
172
  subscribe(listener: () => void): () => void;
99
173
  private setState;
@@ -116,10 +190,44 @@ export declare class AgentSession {
116
190
  attachApp(options: AttachAppOptions): Promise<AppCliAttachment | null>;
117
191
  /** 发送一条消息。字符串或多模态内容均可。 */
118
192
  send(content: MessageContent, options?: SendOptions): Promise<boolean>;
193
+ /**
194
+ * 入队一条等待消息(运行中排队 / 暂停期间照常入队)。
195
+ *
196
+ * ack 超时或传输失败时结果未知:用**同一个** `client_request_id` 重试一次,
197
+ * 服务端按 `(session_id, client_request_id)` 幂等,不会制造第二条。
198
+ */
199
+ queueMessage(content: string): Promise<QueueOperationResult>;
200
+ /** 修改一条等待消息的正文;`expectedVersion` 是客户端看到的条目版本。 */
201
+ updateQueuedMessage(itemId: string, expectedVersion: number, content: string): Promise<QueueOperationResult>;
202
+ /**
203
+ * 重排等待消息。`orderedIds` 必须是当前 pending 条目 id 的**完整集合**(顺序任意),
204
+ * 服务端按 `expectedRevision` 校验后整体替换顺序。
205
+ *
206
+ * 拖动排序就该像本地操作一样落地:版本冲突(运行中的任务刚领走一条 / 另一个端也在
207
+ * 动队列)不甩给用户「刷新后重试」,而是拿冲突响应里那份**最新快照**重算一次再提交
208
+ * ——掉队的条目(已经被领取执行的)从顺序里剔除,剩下的保持用户拖出来的相对次序。
209
+ * 最多补两次;仍旧冲突才把结果交回调用方。
210
+ */
211
+ reorderQueuedMessages(expectedRevision: number, orderedIds: string[]): Promise<QueueOperationResult>;
212
+ /** 删除一条等待消息;不传版本时由服务端自行判定。 */
213
+ cancelQueuedMessage(itemId: string, expectedVersion?: number): Promise<QueueOperationResult>;
214
+ /** 立即补充:把等待消息交给当前运行,在下一个接收点消费,不停止也不另起一轮。 */
215
+ deliverQueuedMessage(itemId: string, expectedVersion: number): Promise<QueueOperationResult>;
216
+ /** 继续执行:清除暂停,并按当前顺序恢复逐条接续。 */
217
+ resumeQueue(): Promise<QueueOperationResult>;
218
+ /** 拉取一份权威队列快照。首次连接与每次重连后由实例自动调用,也可以手动刷新。 */
219
+ refreshQueue(): Promise<QueueOperationResult>;
220
+ /**
221
+ * 发出一条队列命令并安装 ack 里的快照。
222
+ * 传输层失败(超时 / 断线)会 reject,由调用方决定是否重试。
223
+ */
224
+ private requestQueueAck;
225
+ /** 队列命令的对外入口:传输失败只体现为失败结果,不向调用方抛异常。 */
226
+ private runQueueCommand;
119
227
  private sendUnlocked;
120
228
  private rollbackOptimisticSend;
121
- /** 智能体运行期间追加补充说明(不打断当前回复)。 */
122
- append(text: string): void;
229
+ /** 撤回一次乐观提交:移除乐观消息、恢复提问作答、清掉回放草稿。 */
230
+ private discardOptimisticSend;
123
231
  /** 停止当前回复。 */
124
232
  stop(): Promise<Record<string, unknown>>;
125
233
  /** 手动触发上下文压缩。 */
@@ -146,10 +254,17 @@ export declare class AgentSession {
146
254
  get isDisposed(): boolean;
147
255
  /** @internal 连接状态变化(同 client 下所有会话共享一条连接)。 */
148
256
  _setConnection(connection: ConnectionStatus): void;
257
+ /**
258
+ * @internal 服务端广播的权威队列快照。
259
+ * 结构不合法或 revision 不新的一律丢弃,绝不用旧快照覆盖新状态。
260
+ */
261
+ _handleQueueUpdated(payload: unknown): void;
149
262
  /** @internal socket 重连后由 Hub 调用:重新加入房间并补数。 */
150
263
  _rejoinAfterReconnect(): Promise<void>;
151
264
  /** @internal 首次连接:加载历史 + 加入房间。 */
152
265
  _bootstrap(): Promise<void>;
266
+ /** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
267
+ private enqueueSubscription;
153
268
  /** 改回放是 owner 专属(服务端 PATCH 走 _get_owned_session)。 */
154
269
  private get canControlReplay();
155
270
  /** 拉一次会话详情,填回放状态和只读身份;失败按非回放会话处理,不挡启动。 */
@@ -167,7 +282,7 @@ export declare class AgentSession {
167
282
  /** @internal */
168
283
  _handleTurnEnd(turn: TurnProjection): void;
169
284
  /** @internal 历史回放通道(event_format: raw)。 */
170
- _handleTurnEvents(events: RawEvent[]): void;
285
+ _handleTurnEvents(events: RawEvent[], replayed?: boolean): void;
171
286
  /** @internal */
172
287
  _handleChatStart(): void;
173
288
  /** @internal */
@@ -200,10 +315,13 @@ export declare class AgentSession {
200
315
  /** @internal */
201
316
  _handleArtifact(event: AgentSessionEvents["artifact"]): void;
202
317
  /** @internal 加入房间的应答里带 is_running,据此校准流式状态。 */
203
- _applyRunningState(isRunning: boolean): void;
318
+ _applyRunningState(isRunning: boolean, liveRevisionAtJoin?: number): void;
204
319
  private markRunningIfIdle;
205
320
  private finishStreaming;
206
321
  private completeChat;
322
+ private markTurnFresh;
323
+ private markOptimisticTurnPending;
324
+ private pruneOptimisticLiveRevisions;
207
325
  private applyTurn;
208
326
  private schedulePatchFlush;
209
327
  private cancelPatchFlush;
@@ -211,18 +329,26 @@ export declare class AgentSession {
211
329
  /** turn 数据落地后统一派生语义事件(指令 / 工具调用 / 消息 / 模式 / 预览 / OOM)。 */
212
330
  private emitTurnDerivedEvents;
213
331
  private dispatchCommand;
332
+ private dispatchRawCommands;
333
+ private deliverCommand;
334
+ private deliverCommandNow;
214
335
  private notifySandboxOom;
215
- private syncTurnsFromHistory;
216
336
  /**
217
- * 把历史里的指令补投给宿主页面。
337
+ * 权威刷新(运行结束 / 重连补数):**按当前窗口第一条发言取窗口**,不再另记"用户想看哪段"。
218
338
  *
219
- * 重开会话、断线补数时,地图高亮这类"页面当前状态"必须能恢复——
220
- * 否则刷新一次页面,之前智能体让页面做的事就全丢了。
221
- * 只回放指令:toolCall / message 这些事件如果按历史全量重放,
222
- * 监听器会在挂载瞬间收到几百条陈年事件。
223
- * dispatch 内部按 deliveredCommandKeys 去重,重复调用安全。
339
+ * 窗口本身就是"用户在看哪段":首屏就是最新一页,向上翻页 / 定位之后首条就是用户滚到的位置。
340
+ * 直接拿 `from_entry_id = 当前窗口第一条 turn_id` 去取,运行结束、重连补数、翻页之后的对账
341
+ * 是同一条规则,不需要意图、锚点、已落地锚点这些状态;窗口首条已经不在分支上(404 / 409)时
342
+ * 回落到最新一页。
224
343
  */
225
- private replayCommandsFromTurns;
344
+ private refreshAuthoritativeHistory;
345
+ private replaceFromHistory;
346
+ private invalidateOlderPaging;
347
+ private beginCommandRecovery;
348
+ private scheduleHistoricalCommandRestore;
349
+ private flushBufferedCommands;
350
+ /** 首次订阅前的 REST 历史建立语义事件基线,随后同一工具的订阅回放不再投递。 */
351
+ private seedToolCallDeliveryBaseline;
226
352
  private ensureJoined;
227
353
  private ensureIdleBeforeSend;
228
354
  }
@@ -1,4 +1,5 @@
1
1
  import type { ChatMessage, ToolCallInfo } from "../schemas/message";
2
+ import type { McpAppData } from "../schemas/mcp-app";
2
3
  import type { TurnProjection } from "../schemas/projection";
3
4
  import type { SessionStatus } from "../schemas/session";
4
5
  /**
@@ -23,6 +24,8 @@ export interface AgentSessionEvents {
23
24
  toolResult: {
24
25
  toolCall: ToolCallInfo;
25
26
  turn: TurnProjection;
27
+ /** 实时到达,或首次连接/断线重连时由服务端回放。 */
28
+ source: "live" | "bootstrap_replay" | "reconnect_replay";
26
29
  };
27
30
  /** 一轮回复结束。status: completed / paused / interrupted / failed。 */
28
31
  chatEnd: {
@@ -78,6 +81,7 @@ export interface AgentSessionEvents {
78
81
  type: "resource-html" | "resource-uri";
79
82
  content: string;
80
83
  title: string;
84
+ mcpApp?: McpAppData;
81
85
  };
82
86
  /** 任务清单(TaskCreate 等)更新。 */
83
87
  taskListUpdated: {
@@ -0,0 +1,9 @@
1
+ import type { TurnProjection } from "../schemas/projection";
2
+ export interface LiveRevisionState {
3
+ current: number;
4
+ byTurnId: ReadonlyMap<string, number>;
5
+ }
6
+ /** Add an older authoritative slice without allowing it to roll back live content. */
7
+ export declare function prependOlder(current: TurnProjection[], page: TurnProjection[]): TurnProjection[];
8
+ /** Install a new authoritative window, retaining only live writes newer than the request. */
9
+ export declare function replaceWindow(current: TurnProjection[], page: TurnProjection[], liveRevisionAtStart: number, liveRevisions: LiveRevisionState): TurnProjection[];
@@ -1,6 +1,13 @@
1
1
  import type { BladeClient } from "../blade-client";
2
2
  import { AgentSession } from "./agent-session";
3
3
  import type { ConnectionStatus } from "./state";
4
+ export interface SessionConnectOptions {
5
+ /**
6
+ * 在会话进入事件路由后、历史加载与房间订阅开始前同步执行。
7
+ * 适合安装不能漏掉 bootstrap 回放事件的监听器。
8
+ */
9
+ setup?: (session: AgentSession) => void;
10
+ }
4
11
  /**
5
12
  * 每个 BladeClient 一个:管理一条 Socket.IO 连接,按 session_id 把事件
6
13
  * 路由给各 AgentSession 实例。多会话共享连接、状态互不干扰。
@@ -15,6 +22,7 @@ export declare class SessionHub {
15
22
  private readonly connectionListeners;
16
23
  private bound;
17
24
  private hasConnected;
25
+ private readonly authBusyReconnect;
18
26
  constructor(client: BladeClient);
19
27
  /** 当前 Socket.IO 连接状态。 */
20
28
  getConnection(): ConnectionStatus;
@@ -27,7 +35,7 @@ export declare class SessionHub {
27
35
  * 只有最后一份归还才退订房间——否则 StrictMode 双跑、路由抖动这类"连两次
28
36
  * 只释放一次"的场景会把仍在用的会话退订掉,之后所有 turn/chat 事件静默丢失。
29
37
  */
30
- connect(sessionId: string): Promise<AgentSession>;
38
+ connect(sessionId: string, options?: SessionConnectOptions): Promise<AgentSession>;
31
39
  /** 已有实例则返回,不触发网络请求。 */
32
40
  peek(sessionId: string): AgentSession | undefined;
33
41
  /** 手动重连(例如登录态变化后)。 */
@@ -0,0 +1,99 @@
1
+ /**
2
+ * 会话消息队列的共享领域模型:类型、快照合并规则与中文文案。
3
+ *
4
+ * 这里是**单一定义处**:AgentSession 的状态机、agent-react 与内置 Web 的队列 UI
5
+ * 都只用这一份判定,避免"能不能编辑 / 旧快照要不要覆盖"各写一遍后各自演进。
6
+ * 服务端语义见 issue #2529 与 local://frontend-queue-contract.md。
7
+ */
8
+ /** 等待消息的服务端生命周期状态。 */
9
+ export type QueuedMessageStatus = "pending" | "delivering" | "claimed" | "consumed" | "cancelled";
10
+ /** 队列里的一条等待消息;权威快照只包含 pending(等待执行)与 delivering(等待接收)。 */
11
+ export interface QueuedMessage {
12
+ id: string;
13
+ content: string;
14
+ position: number;
15
+ version: number;
16
+ status: QueuedMessageStatus;
17
+ created_at: string;
18
+ updated_at: string;
19
+ /** 立即补充指向的当前运行 id;普通等待条目没有。 */
20
+ target_run_id?: string;
21
+ /** 领取或投递失败的原因。 */
22
+ error?: string;
23
+ }
24
+ /** 服务端权威队列快照。`revision` 单调递增,客户端据此丢弃过期快照。 */
25
+ export interface SessionQueueSnapshot {
26
+ session_id: string;
27
+ revision: number;
28
+ paused: boolean;
29
+ pause_reason: string | null;
30
+ items: QueuedMessage[];
31
+ }
32
+ /**
33
+ * 还没有拿到服务端快照时的占位。
34
+ *
35
+ * `session_id` 为空表示"身份未知":`shouldApplySnapshot` 会跳过会话身份校验、
36
+ * 只比较 revision,这样第一份快照(哪怕 revision 为 0 的暂停态)也能装进来。
37
+ */
38
+ export declare const EMPTY_SESSION_QUEUE: SessionQueueSnapshot;
39
+ /**
40
+ * 队列命令的 ack 线格式。
41
+ *
42
+ * `conflict` 表示乐观并发或状态校验失败,此时快照字段是服务端最新值,客户端必须
43
+ * 据此刷新本地并保留用户草稿;`error` 时快照字段可能缺失(如鉴权失败),此时只报错、
44
+ * 不改动本地队列。
45
+ */
46
+ export interface QueueAck {
47
+ /** `queued` 只出现在 `chat:send` 的运行中改道:消息已进等待队列,不是失败。 */
48
+ status: "ok" | "conflict" | "error" | "queued";
49
+ /**
50
+ * 只有 `queued` 会带:服务端判定这次改道时该会话是否有运行。
51
+ * 客户端据此对齐流式标记,不用自己的猜测。
52
+ */
53
+ running?: boolean;
54
+ message?: string | null;
55
+ session_id?: string;
56
+ revision?: number;
57
+ paused?: boolean;
58
+ pause_reason?: string | null;
59
+ items?: QueuedMessage[];
60
+ }
61
+ /** 解析后的队列操作结果;UI 只看这一个结构。 */
62
+ export interface QueueOperationResult {
63
+ ok: boolean;
64
+ conflict: boolean;
65
+ /** 服务端快照;缺失时为 null,调用方不得据此清空本地队列。 */
66
+ snapshot: SessionQueueSnapshot | null;
67
+ message: string | null;
68
+ }
69
+ /** 判定一份载荷是不是完整的权威队列快照(广播事件用)。 */
70
+ export declare function isSessionQueueSnapshot(value: unknown): value is SessionQueueSnapshot;
71
+ /**
72
+ * 是否用 `next` 覆盖 `current`。
73
+ *
74
+ * 规则只有两条:会话身份必须一致(还没拿到过快照时不校验),且 revision 必须严格
75
+ * 更新——相等或更旧一律不采纳,乱序到达的旧快照不会把新状态顶回去。
76
+ */
77
+ export declare function shouldApplySnapshot(current: SessionQueueSnapshot | undefined, next: SessionQueueSnapshot): boolean;
78
+ /**
79
+ * 解析 `emitWithAck` 的返回值。
80
+ *
81
+ * - `ok` / `queued` / `conflict` 且能解析成快照 → `snapshot` 有值;
82
+ * - `queued` → `ok=true`:`chat:send` 在运行中改道进了等待队列,调用方要撤掉乐观消息;
83
+ * - `conflict` → `ok=false, conflict=true`(快照可能仍缺失,调用方按 null 处理);
84
+ * - `error` 或结构不合法 → `ok=false, conflict=false`,`message` 为服务端文案或兜底。
85
+ */
86
+ export declare function parseQueueAck(response: unknown, fallbackSessionId: string): QueueOperationResult;
87
+ /** 等待中的条目才能改正文;已经交给运行或开始执行的条目不再可编辑。 */
88
+ export declare function canEditQueuedMessage(item: QueuedMessage): boolean;
89
+ /** 等待中的条目才能删除。 */
90
+ export declare function canCancelQueuedMessage(item: QueuedMessage): boolean;
91
+ /**
92
+ * 能否立即补充。条目本身必须是等待中的;"会话是否正在运行"由调用方判断——
93
+ * 服务端会在没有可接收补充的任务时返回冲突。
94
+ */
95
+ export declare function canDeliverQueuedMessage(item: QueuedMessage): boolean;
96
+ /** 面向前端展示的状态文案;已离开队列视图的状态没有文案。 */
97
+ export declare function queuedMessageStatusLabel(status: QueuedMessageStatus): string;
98
+ /** 暂停原因文案;未知原因原样透出,没有原因返回 null。 */
99
+ export declare function queuePauseReasonLabel(reason: string | null | undefined): string | null;
@@ -1,10 +1,13 @@
1
1
  import type { ChatMessage, CompactionInfo, MessageContent } from "../schemas/message";
2
2
  import type { ContentBlock, PatchEnvelope, TurnProjection } from "../schemas/projection";
3
3
  import type { ReplaySpeed, ReplayState, SessionStatus } from "../schemas/session";
4
- /** 一次 AskUserQuestion 的用户作答(选项选择 + 自定义输入)。 */
4
+ import { type SessionQueueSnapshot } from "./queue";
5
+ /** 一次 AskUserQuestion 的用户作答(选项选择 + 自定义输入 + 可选补充)。 */
5
6
  export interface AskUserAnswerData {
6
7
  selections: Record<number, number[]>;
7
8
  custom: Record<number, string>;
9
+ /** 选完选项后的自由补充,空着就是没有。 */
10
+ note?: string;
8
11
  }
9
12
  export interface AgentLoopInfo {
10
13
  toolCallId: string;
@@ -41,8 +44,13 @@ export interface SessionState {
41
44
  activeCompaction: ActiveCompactionState | null;
42
45
  /** 回放(演示 / 彩排)状态;不是回放会话时为 null。 */
43
46
  replay: ReplaySnapshot | null;
47
+ /** 服务端持久化的等待消息队列快照;只在 revision 更新时替换。 */
48
+ queue: SessionQueueSnapshot;
44
49
  /** 当前身份对这个会话的权限;"viewer" 表示只读(分享链接打开的)。 */
45
50
  viewerRole: "owner" | "viewer" | null;
51
+ /** Stable anchor for the next older projected page. */
52
+ nextBefore: string | null;
53
+ loadingOlder: boolean;
46
54
  }
47
55
  /** 回放会话的快照视图,由服务端的 replay_state 归一化而来。 */
48
56
  export interface ReplaySnapshot {
@@ -56,6 +64,9 @@ export declare function toReplaySnapshot(raw: ReplayState | null | undefined): R
56
64
  export declare function createInitialSessionState(sessionId: string): SessionState;
57
65
  export declare function extractModeFromBlocks(blocks: ContentBlock[]): "planning" | "executing" | null;
58
66
  export declare function reconcileOptimisticUserTurns(currentTurns: TurnProjection[], incomingTurns: TurnProjection[]): TurnProjection[];
67
+ /** Reconcile a bulk authoritative window only by an echoed request identity.
68
+ * Positional fallback is reserved for a single live turn from an older server. */
69
+ export declare function reconcileHistoricalUserTurns(currentTurns: TurnProjection[], incomingTurns: TurnProjection[]): TurnProjection[];
59
70
  /**
60
71
  * 用一份新的 turn 列表重建会话状态。
61
72
  * addErrorMessage 注入的 role==="error" 消息不在 turns 里,默认会被物化过程抹掉;
@@ -63,7 +74,12 @@ export declare function reconcileOptimisticUserTurns(currentTurns: TurnProjectio
63
74
  * turn 推进(新消息 / rewind / 重连)时旧错误自动消失。
64
75
  */
65
76
  export declare function withTurns(state: SessionState, turns: TurnProjection[]): SessionState;
66
- export declare function addUserMessage(state: SessionState, content: MessageContent): SessionState;
77
+ /**
78
+ * 安装一份服务端权威队列快照。旧 revision、别的会话或结构不合法的快照一律不采纳,
79
+ * 直接返回原 state——引用不变,订阅者不会收到多余通知。
80
+ */
81
+ export declare function withQueue(state: SessionState, snapshot: SessionQueueSnapshot): SessionState;
82
+ export declare function addUserMessage(state: SessionState, content: MessageContent, clientRequestId?: string): SessionState;
67
83
  export declare function upsertTurn(state: SessionState, turn: TurnProjection): SessionState;
68
84
  export declare function applyTurnPatch(state: SessionState, patch: PatchEnvelope): SessionState;
69
85
  export declare function addErrorMessage(state: SessionState, content: string): SessionState;
@@ -0,0 +1,9 @@
1
+ /** Return the server-requested delay for a typed temporary auth rejection. */
2
+ export declare function authBusyRetryDelayMs(error: unknown): number | null;
3
+ /** One pending manual reconnect for Socket.IO CONNECT_ERROR auth overloads. */
4
+ export declare class AuthBusyReconnect {
5
+ private timer;
6
+ get pending(): boolean;
7
+ schedule(error: unknown, reconnect: () => void): boolean;
8
+ cancel(): void;
9
+ }
@@ -3,6 +3,8 @@ export interface RawEvent {
3
3
  type: string;
4
4
  payload: Record<string, unknown>;
5
5
  }
6
+ /** 在任何事件处理前确认 Socket.IO 批次已到达客户端。 */
7
+ export declare function acknowledgeTurnEvents(acknowledge?: () => void): void;
6
8
  export type ProjectionUpdate = {
7
9
  kind: "upsert";
8
10
  turn: TurnProjection;
@@ -13,6 +15,8 @@ export type ProjectionUpdate = {
13
15
  toolName?: string;
14
16
  workspacePath?: string;
15
17
  };
18
+ /** 是否包含会改变聊天运行生命周期的事件。纯观测/整理事件不启动新一轮。 */
19
+ export declare function hasChatRunEvent(events: RawEvent[]): boolean;
16
20
  /**
17
21
  * 缺失 chat:end 时的终态兜底。只有被投影接受到当前 assistant turn 的 followup
18
22
  * 才能结束流式态;陈旧 followup 或其后已有新轮事件时均返回 false。
@@ -22,7 +26,10 @@ export declare class ClientProjectionBuilder {
22
26
  private activeTurns;
23
27
  private lastTurnIds;
24
28
  private latestTurns;
29
+ private turnsById;
25
30
  private loopDescriptions;
31
+ /** loop_name → 正在运行的子智能体起点,agent:end 结算并挂到父工具调用上。 */
32
+ private activeSubagentRuns;
26
33
  private pendingChildPauses;
27
34
  private pendingMemoryRefs;
28
35
  private activeCompactions;
@@ -37,6 +44,7 @@ export declare class ClientProjectionBuilder {
37
44
  seedSequence(maxSeq: number): void;
38
45
  _seedTurns(turns: TurnProjection[]): void;
39
46
  private onMemoryInject;
47
+ private onContextEntry;
40
48
  private onLoopTurn;
41
49
  private onModeChange;
42
50
  private onPlanStatusUpdate;
@@ -49,7 +57,10 @@ export declare class ClientProjectionBuilder {
49
57
  private onPostChatFollowup;
50
58
  private onDelta;
51
59
  private onToolCallCreated;
60
+ private onToolCallArgumentsDelta;
52
61
  private onResponseDone;
62
+ private applyFirstTokenDelivery;
63
+ private applyStreamTiming;
53
64
  private onToolResultDone;
54
65
  private onToolResultDelta;
55
66
  private onToolUi;
@@ -58,6 +69,7 @@ export declare class ClientProjectionBuilder {
58
69
  private onChildPause;
59
70
  private onChatEnd;
60
71
  private onAgentEnd;
72
+ private settleSubagentRun;
61
73
  private dropIncompleteToolCalls;
62
74
  private onCompaction;
63
75
  private syncPatch;
@@ -0,0 +1,3 @@
1
+ import type { TurnProjection } from "../../schemas/projection";
2
+ /** Build one Context turn from the canonical entry used by live and history paths. */
3
+ export declare function buildContextTurn(entry: Record<string, unknown>, sequence: number): TurnProjection | null;
@@ -1,10 +1,10 @@
1
1
  import type { ContentBlock, PendingQuestionRef, ToolCallProjection, TurnProjection } from "../../schemas/projection";
2
2
  import type { TurnState } from "./state";
3
3
  export declare function upsertToolCall(state: TurnState, toolCallId: string, toolName: string, displayName: string, args: string): void;
4
- export declare function applyToolResult(state: TurnState, toolCallId: string, result: unknown, status: ToolCallProjection["status"], durationMs: number | null, sourceLoop: {
4
+ export declare function applyToolResult(state: TurnState, toolCallId: string, result: unknown, status: ToolCallProjection["status"], durationMs: number | null, ranInParallel: boolean | null, sourceLoop: {
5
5
  loop_name: string;
6
6
  description: string;
7
- } | null): void;
7
+ } | null, startedAt?: string | null): void;
8
8
  export declare function appendToolResultDelta(state: TurnState, toolCallId: string, delta: string, reset?: boolean): void;
9
9
  export declare function appendTextBlock(state: TurnState, blockType: ContentBlock["type"], content: unknown): void;
10
10
  export declare function appendToolCallArguments(state: TurnState, toolCallId: string, delta: string): void;