@blade-hq/agent-client 2610.0.0-beta.4 → 2610.0.0-beta.41

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.
@@ -0,0 +1,14 @@
1
+ export declare const PLATFORM_SERVICE_NAMES: readonly ["os", "hub", "agent", "oauth", "llmGateway", "gitea", "tile"];
2
+ export type PlatformServiceName = (typeof PLATFORM_SERVICE_NAMES)[number];
3
+ export interface PlatformEndpoints {
4
+ services: Partial<Record<PlatformServiceName, string>>;
5
+ }
6
+ export declare const EMPTY_PLATFORM_ENDPOINTS: PlatformEndpoints;
7
+ export interface LoadPlatformEndpointsOptions {
8
+ baseUrl?: string;
9
+ fetchImpl?: typeof fetch;
10
+ timeoutMs?: number;
11
+ }
12
+ export declare function parsePlatformEndpoints(value: unknown): PlatformEndpoints | null;
13
+ export declare function loadPlatformEndpoints(options?: LoadPlatformEndpointsOptions): Promise<PlatformEndpoints>;
14
+ export declare function resolveServiceUrl(endpoints: PlatformEndpoints, name: PlatformServiceName, path?: string): string | null;
@@ -0,0 +1,85 @@
1
+ import type { BladeClient } from "../blade-client";
2
+ /** 一台远程电脑在某个会话眼里的样子。 */
3
+ export interface SessionComputer {
4
+ id: string;
5
+ label: string;
6
+ hostname?: string | null;
7
+ os?: string | null;
8
+ arch?: string | null;
9
+ /** 那台机器的用户主目录,命令默认在这里执行。 */
10
+ home?: string | null;
11
+ /** daemon 启动时所在的目录。 */
12
+ workspace?: string | null;
13
+ blade_home?: string | null;
14
+ allowed_paths?: string[];
15
+ agent_runtimes?: unknown[];
16
+ daemon_version?: string | null;
17
+ created_at: string;
18
+ last_seen_at?: string | null;
19
+ /** 这台电脑此刻连着后端。由服务端读心跳时间判定,不做实时探测。 */
20
+ online: boolean;
21
+ /** 这次会话已经启动了它。和 `online` 互相独立。 */
22
+ enabled: boolean;
23
+ /** 这次会话本身就跑在这台电脑上。 */
24
+ is_primary: boolean;
25
+ }
26
+ export interface SessionComputerList {
27
+ computers: SessionComputer[];
28
+ }
29
+ /**
30
+ * 一台电脑在界面上该显示成什么状态。
31
+ *
32
+ * - `primary` 会话自己就住在这台机器上,恒为可用且不能停用
33
+ * - `enabled` 本次会话已启动
34
+ * - `offline` 没启动且当前离线
35
+ * - `idle` 没启动,在线
36
+ *
37
+ * 判定只写这一份,界面只管样式和排版——两边各写一份必然会分叉,
38
+ * 然后其中一边的兜底会把另一边的错误盖住。
39
+ */
40
+ export type ComputerState = "primary" | "enabled" | "offline" | "idle";
41
+ export declare function computerState(computer: SessionComputer): ComputerState;
42
+ /**
43
+ * 这台电脑跑的是哪一类系统。
44
+ *
45
+ * 判定放这里、图标映射留给界面:`os` 是 daemon 上报的 `runtime.GOOS`,
46
+ * 各处自己 `startsWith` 一遍必然会分叉。
47
+ */
48
+ export type ComputerOS = "macos" | "windows" | "linux" | "unknown";
49
+ export declare function computerOS(computer: SessionComputer): ComputerOS;
50
+ /** 系统与架构,例如 `darwin/arm64`。信息不全时尽量给出已知的那部分。 */
51
+ export declare function computerPlatformLabel(computer: SessionComputer): string;
52
+ /**
53
+ * 那台电脑上 blade daemon 的版本。
54
+ *
55
+ * 服务端存的就是 daemon 上报的完整串(dev 构建带 commit),这里原样用,
56
+ * 不在前端另拼一套格式——拼两份迟早对不上。
57
+ */
58
+ export declare function computerDaemonVersion(computer: SessionComputer): string;
59
+ /** 这台电脑的勾选框能不能动。主运行时不能停用——那是会话自己的工作目录所在。 */
60
+ export declare function canToggleComputer(computer: SessionComputer): boolean;
61
+ /**
62
+ * 列表顺序:按接入时间,稳定不变。
63
+ *
64
+ * **刻意不按在线/可用排序。** 那样排看着"手边的在前面",代价是电脑上下线、
65
+ * 勾选状态一变,整个列表就重排——用户正要点的那一项会在手指底下跑掉。
66
+ * 一个会自己动的列表比一个排序不那么聪明的列表难用得多。
67
+ *
68
+ * 用 `created_at` 而不是名字:改名不会让位置变,新接入的稳定排在末尾。
69
+ */
70
+ export declare function sortComputers(computers: SessionComputer[]): SessionComputer[];
71
+ /** 会话可以操作哪些远程电脑。 */
72
+ export declare class ComputersResource {
73
+ private client;
74
+ constructor(client: BladeClient);
75
+ /**
76
+ * 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
77
+ * 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
78
+ */
79
+ list(sessionId: string): Promise<SessionComputerList>;
80
+ /**
81
+ * 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
82
+ * 电脑离线时同样能登记,等它上线就直接可用。
83
+ */
84
+ setEnabled(sessionId: string, computer: string, enabled: boolean): Promise<SessionComputer>;
85
+ }
@@ -12,6 +12,8 @@ export interface ModelOption {
12
12
  export interface ModelCatalog {
13
13
  default: string;
14
14
  models: ModelOption[];
15
+ unavailableProviderIds?: string[];
16
+ unavailableModelIds?: string[];
15
17
  /**
16
18
  * 平台默认模型服务的 OpenAI 兼容地址(Server ≥ 提供 `baseUrl` 的版本才有,否则为空)。
17
19
  * 自建应用可以带用户的 PAT 直接调它,不必让用户自己填地址——网关端口各部署不同,
@@ -24,8 +24,10 @@ export interface CreateSessionRequest {
24
24
  disable_tools?: string[] | null;
25
25
  session_solution_asset_id?: string | null;
26
26
  compaction_ratio?: number | null;
27
- runtime_type?: "sandbox" | "daemon" | null;
27
+ runtime_type?: "sandbox" | "daemon" | "native" | null;
28
28
  daemon_id?: string | null;
29
+ /** runtime_type 为 "native" 时必填:daemon 上要用哪个原生智能体(如 rt_codex)。 */
30
+ agent_runtime_id?: string | null;
29
31
  }
30
32
  export interface CheckpointNode {
31
33
  id: string;
@@ -126,6 +128,17 @@ export interface SessionContextStats {
126
128
  context_window: number;
127
129
  compaction_ratio: number;
128
130
  }
131
+ export interface SessionTurnsPage {
132
+ turns: TurnProjection[];
133
+ nextBefore: string | null;
134
+ currentMode: "planning" | "executing";
135
+ }
136
+ export interface HistoricalCommand {
137
+ deliveryId: string;
138
+ toolCallId?: string;
139
+ action: string;
140
+ payload?: unknown;
141
+ }
129
142
  export type ResultFeedbackReason = "not_solved" | "inaccurate" | "incomplete" | "needs_major_changes" | "too_slow" | "other";
130
143
  export interface ResultFeedback {
131
144
  session_id: string;
@@ -262,6 +275,11 @@ export declare class SessionsResource {
262
275
  getSharedSession(token: string): Promise<TurnProjection[]>;
263
276
  getSessionTasks(sessionId: string): Promise<Task[]>;
264
277
  getSessionTurns(sessionId: string): Promise<TurnProjection[]>;
278
+ getSessionTurnsPage(sessionId: string, options?: {
279
+ limit?: number;
280
+ before?: string;
281
+ }): Promise<SessionTurnsPage>;
282
+ getSessionTurnCommands(sessionId: string): Promise<HistoricalCommand[]>;
265
283
  listResultFeedback(sessionId: string): Promise<ResultFeedback[]>;
266
284
  putResultFeedback(sessionId: string, assistantEntryId: string, feedback: {
267
285
  helpful: boolean;
@@ -0,0 +1,49 @@
1
+ export type ContextAction = "published" | "retained" | "removed";
2
+ /** Safe display-source metadata retained for headless consumers. */
3
+ export type ContextSourceInfo = Record<string, unknown>;
4
+ export interface ContextProjectionData {
5
+ context_kind: string;
6
+ context_key: string;
7
+ context_name: string;
8
+ context_order: number;
9
+ context_action: ContextAction;
10
+ context_revision: string;
11
+ display_title?: string | null;
12
+ display_summary?: string | null;
13
+ sources: ContextSourceInfo[];
14
+ }
15
+ export interface ContextProjectionFields {
16
+ context_kind?: string | null;
17
+ context_key?: string | null;
18
+ context_name?: string | null;
19
+ context_order?: number | null;
20
+ context_action?: ContextAction | null;
21
+ context_revision?: string | null;
22
+ display_title?: string | null;
23
+ display_summary?: string | null;
24
+ sources?: ContextSourceInfo[] | null;
25
+ }
26
+ export interface ContextDisplayState {
27
+ title: string;
28
+ summary: string;
29
+ detail: string;
30
+ isRemoved: boolean;
31
+ }
32
+ export interface ContextGroupDisplayState {
33
+ title: string;
34
+ summary: string;
35
+ count: number;
36
+ }
37
+ export declare function contextProjectionData(fields: ContextProjectionFields): ContextProjectionData | null;
38
+ /** The single status-to-display mapping used by both React chat surfaces. */
39
+ export declare function getContextDisplayState(context: ContextProjectionData): ContextDisplayState;
40
+ /**
41
+ * 把一段渲染序列里连续的 Context 归成组。
42
+ *
43
+ * 入参是序列里每一项的 context kind,非 Context 项传 null;返回每一段连续
44
+ * Context 的下标数组。纯下标运算,不认识任何 UI 类型,所以调试日志、Lite 视图
45
+ * 和 SDK 卡片可以共用同一份分组判定,各自只管排版。
46
+ */
47
+ export declare function groupAdjacentContextRuns(kinds: readonly (string | null)[]): readonly (readonly number[])[];
48
+ /** 折叠行的文案。展开后才逐条显示各自的标题与状态。 */
49
+ export declare function getContextGroupDisplayState(contexts: readonly ContextProjectionData[]): ContextGroupDisplayState;
@@ -11,6 +11,15 @@ export interface ToolCallCreated {
11
11
  arguments: string;
12
12
  };
13
13
  }
14
+ export interface ToolCallArgumentsDelta {
15
+ index: number;
16
+ id: string;
17
+ loop_name?: string;
18
+ function: {
19
+ name: string;
20
+ };
21
+ arguments_delta: string;
22
+ }
14
23
  export interface ToolResultDone {
15
24
  tool_call_id: string;
16
25
  loop_name?: string;
@@ -1,9 +1,13 @@
1
1
  import type { ChatMessage, FileContentPart, ImageUrlContentPart, MessageContent } from "./message";
2
+ /** Ordinary chat uses business copy; developer mode may retain diagnostics. */
3
+ export declare function chatErrorForDisplay(message: string, developer?: boolean): string;
2
4
  export declare function normalizeMessageContent(content: unknown): MessageContent;
3
5
  export declare function getTextContent(content: MessageContent): string;
4
6
  export declare function isSystemReminderText(text: string): boolean;
5
7
  export declare function isLegacyForkContextText(text: string): boolean;
6
- export declare function isHiddenInternalMessage(message: Pick<ChatMessage, "role" | "content">): boolean;
8
+ export declare function isHiddenInternalMessage(message: Pick<ChatMessage, "role" | "content">, options?: {
9
+ includeLegacyForkContext?: boolean;
10
+ }): boolean;
7
11
  export declare function buildMessageContent(text: string, attachments: Array<{
8
12
  kind: "file";
9
13
  name: string;
@@ -1,4 +1,5 @@
1
1
  import type { ContentBlock, PendingQuestionRef, ToolBridgeContent } from "./projection";
2
+ import type { ContextProjectionData } from "./context";
2
3
  export interface TextContentPart {
3
4
  type: "text";
4
5
  text: string;
@@ -70,6 +71,8 @@ export interface ChatMessage {
70
71
  duration_ms?: number;
71
72
  timestamp?: string;
72
73
  entry_id?: string;
74
+ /** Stable React key retained when an optimistic user message is confirmed. */
75
+ render_id?: string;
73
76
  parent_id?: string | null;
74
77
  loop_name?: string;
75
78
  /** Entry kind from backend -- present for non-message entries like mode changes. */
@@ -80,4 +83,6 @@ export interface ChatMessage {
80
83
  blocks?: ContentBlock[];
81
84
  /** Compaction metadata for compaction turns. */
82
85
  compaction?: CompactionInfo;
86
+ /** Sourced Context metadata for rendering or headless consumers. */
87
+ context?: ContextProjectionData;
83
88
  }
@@ -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;
@@ -22,6 +24,7 @@ export interface ToolCallProjection {
22
24
  status: "pending" | "done" | "error" | "cancelled" | "awaiting_answer" | string;
23
25
  result?: unknown;
24
26
  duration_ms?: number | null;
27
+ ran_in_parallel?: boolean;
25
28
  pending_question_ref?: PendingQuestionRef | null;
26
29
  }
27
30
  export interface MemoryRef {
@@ -47,7 +50,7 @@ export interface TurnProjection {
47
50
  sequence: number;
48
51
  turn_id: string;
49
52
  loop_id: string;
50
- kind?: "message" | "compaction";
53
+ kind?: "message" | "context" | "compaction";
51
54
  role: "user" | "assistant" | "system";
52
55
  status: "streaming" | "completed" | "paused" | "failed" | "interrupted";
53
56
  blocks: ContentBlock[];
@@ -55,10 +58,28 @@ export interface TurnProjection {
55
58
  model?: string | null;
56
59
  usage?: Record<string, unknown> | null;
57
60
  duration_ms?: number;
61
+ first_token_ms?: number | null;
62
+ stream_duration_ms?: number | null;
63
+ first_token_delivery_ms?: number | null;
58
64
  started_at?: string | null;
59
65
  context_window?: number;
60
66
  memory_refs?: MemoryRef[] | null;
61
67
  parent_fork_tool_call_id?: string | null;
68
+ context_kind?: string | null;
69
+ context_key?: string | null;
70
+ context_name?: string | null;
71
+ context_order?: number | null;
72
+ context_action?: import("./context").ContextAction | null;
73
+ context_revision?: string | null;
74
+ /** 原始 Context 内容仅供开发者模式诊断;常规消息投影不消费。 */
75
+ context_content?: unknown;
76
+ display_title?: string | null;
77
+ display_summary?: string | null;
78
+ sources?: import("./context").ContextSourceInfo[] | null;
79
+ /** Client-only stable identity for reconciling an optimistic user turn. */
80
+ client_render_id?: string | null;
81
+ /** Send identity echoed by the server for exact optimistic reconciliation. */
82
+ client_request_id?: string | null;
62
83
  compaction_id?: string | null;
63
84
  summary_preview?: string | null;
64
85
  summary_full?: string | null;
@@ -82,7 +82,10 @@ 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;
86
89
  match?: unknown;
87
90
  }, {}>;
88
91
  export type SessionInfo = Omit<typeof SessionInfo.infer, "template_id" | "solution" | "ports" | "match"> & {
@@ -106,9 +109,12 @@ export type SessionInfo = Omit<typeof SessionInfo.infer, "template_id" | "soluti
106
109
  is_example?: boolean;
107
110
  ports?: SessionPortMapping[];
108
111
  disable_tools?: string[];
109
- runtime_type?: "sandbox" | "daemon" | string | null;
112
+ runtime_type?: "sandbox" | "daemon" | "native" | string | null;
110
113
  daemon_id?: string | null;
114
+ agent_runtime_id?: string | null;
111
115
  workspace_path?: string | null;
116
+ ba_version?: string | null;
117
+ sandbox_version?: string | null;
112
118
  /** 仅在带 q 搜索的列表响应中出现;普通列表里此字段缺省。 */
113
119
  match?: SessionSearchMatch | null;
114
120
  };
@@ -4,15 +4,15 @@ 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, SessionTurnsPage } from "../resources/sessions";
8
8
  import { type ConnectionStatus, type SessionState } from "./state";
9
9
  /** SessionHub 注入给 AgentSession 的运行时依赖(socket 收发 + REST 补数)。 */
10
10
  export interface SessionRuntime {
11
- streamTokens: boolean;
12
11
  emitEvent(event: string, data: unknown): void;
13
12
  emitWithAck(event: string, data: unknown, timeoutMs: number): Promise<Record<string, unknown> | undefined>;
14
13
  ensureConnected(): void;
15
- fetchTurns(sessionId: string): Promise<TurnProjection[]>;
14
+ fetchTurnsPage(sessionId: string, before?: string): Promise<SessionTurnsPage>;
15
+ fetchCommands(sessionId: string): Promise<HistoricalCommand[]>;
16
16
  /** 会话详情,目前只用来取回放状态。 */
17
17
  fetchSessionInfo(sessionId: string): Promise<{
18
18
  replay_state?: ReplayState | null;
@@ -53,6 +53,8 @@ export interface SendOptions {
53
53
  deprecate_entry_ids: string[];
54
54
  };
55
55
  replayDecision?: "keep_replay" | "continue_replay";
56
+ /** 用户 @ 的知识库 id:后端先用原话在这些库里预检索,本轮 kb_search 也默认限定在这些库。 */
57
+ kbIds?: number[];
56
58
  }
57
59
  export declare function sessionStatusFromChatEnd(status: string | undefined): SessionStatus;
58
60
  /**
@@ -75,10 +77,29 @@ export declare class AgentSession {
75
77
  private patchFlushCancel;
76
78
  private joined;
77
79
  private joinPending;
80
+ private subscriptionQueue;
81
+ private replaceQueue;
82
+ private timelineEpoch;
83
+ private pagingGeneration;
84
+ private liveRevision;
85
+ private modeRevision;
86
+ private readonly liveRevisions;
87
+ private readonly pendingOptimisticTurnIds;
88
+ /** Consecutive supplements are persisted as one authoritative user turn. */
89
+ private pendingAppendTurnId;
90
+ private olderPending;
91
+ private replaceInFlight;
78
92
  private replayQueue;
93
+ private commandRestoreQueue;
94
+ private commandRecoveryGeneration;
95
+ private commandRestoreRetryAttempt;
96
+ private commandRestoreRetryHandle;
97
+ private bufferedCommandDeliveries;
79
98
  private pendingReplayMessage;
80
99
  private pendingReplayMode;
100
+ private pendingReplayKbIds;
81
101
  private recentChatEndAt;
102
+ private subscriptionReplaySource;
82
103
  private disposed;
83
104
  private getAppContext;
84
105
  private sendPending;
@@ -94,6 +115,11 @@ export declare class AgentSession {
94
115
  static fromTurns(sessionId: string, turns: TurnProjection[]): AgentSession;
95
116
  /** 返回当前会话快照。引用稳定:状态未变化时返回同一对象。 */
96
117
  getState(): SessionState;
118
+ get hasOlderHistory(): boolean;
119
+ /** Load the page immediately preceding the current display window. */
120
+ loadOlderHistory(options?: {
121
+ beforeCommit?: () => void;
122
+ }): Promise<void>;
97
123
  /** 订阅状态变化。返回取消订阅函数。 */
98
124
  subscribe(listener: () => void): () => void;
99
125
  private setState;
@@ -116,6 +142,13 @@ export declare class AgentSession {
116
142
  attachApp(options: AttachAppOptions): Promise<AppCliAttachment | null>;
117
143
  /** 发送一条消息。字符串或多模态内容均可。 */
118
144
  send(content: MessageContent, options?: SendOptions): Promise<boolean>;
145
+ /** Queue a plain-text prompt while a turn is streaming. */
146
+ queue(content: string): Promise<boolean>;
147
+ listQueue(): Promise<Array<{
148
+ id: number;
149
+ message: string;
150
+ }>>;
151
+ cancelQueued(id: number): Promise<boolean>;
119
152
  private sendUnlocked;
120
153
  private rollbackOptimisticSend;
121
154
  /** 智能体运行期间追加补充说明(不打断当前回复)。 */
@@ -150,6 +183,8 @@ export declare class AgentSession {
150
183
  _rejoinAfterReconnect(): Promise<void>;
151
184
  /** @internal 首次连接:加载历史 + 加入房间。 */
152
185
  _bootstrap(): Promise<void>;
186
+ /** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
187
+ private enqueueSubscription;
153
188
  /** 改回放是 owner 专属(服务端 PATCH 走 _get_owned_session)。 */
154
189
  private get canControlReplay();
155
190
  /** 拉一次会话详情,填回放状态和只读身份;失败按非回放会话处理,不挡启动。 */
@@ -167,7 +202,7 @@ export declare class AgentSession {
167
202
  /** @internal */
168
203
  _handleTurnEnd(turn: TurnProjection): void;
169
204
  /** @internal 历史回放通道(event_format: raw)。 */
170
- _handleTurnEvents(events: RawEvent[]): void;
205
+ _handleTurnEvents(events: RawEvent[], replayed?: boolean): void;
171
206
  /** @internal */
172
207
  _handleChatStart(): void;
173
208
  /** @internal */
@@ -200,10 +235,13 @@ export declare class AgentSession {
200
235
  /** @internal */
201
236
  _handleArtifact(event: AgentSessionEvents["artifact"]): void;
202
237
  /** @internal 加入房间的应答里带 is_running,据此校准流式状态。 */
203
- _applyRunningState(isRunning: boolean): void;
238
+ _applyRunningState(isRunning: boolean, liveRevisionAtJoin?: number): void;
204
239
  private markRunningIfIdle;
205
240
  private finishStreaming;
206
241
  private completeChat;
242
+ private markTurnFresh;
243
+ private markOptimisticTurnPending;
244
+ private pruneOptimisticLiveRevisions;
207
245
  private applyTurn;
208
246
  private schedulePatchFlush;
209
247
  private cancelPatchFlush;
@@ -211,18 +249,17 @@ export declare class AgentSession {
211
249
  /** turn 数据落地后统一派生语义事件(指令 / 工具调用 / 消息 / 模式 / 预览 / OOM)。 */
212
250
  private emitTurnDerivedEvents;
213
251
  private dispatchCommand;
252
+ private dispatchRawCommands;
253
+ private deliverCommand;
254
+ private deliverCommandNow;
214
255
  private notifySandboxOom;
215
- private syncTurnsFromHistory;
216
- /**
217
- * 把历史里的指令补投给宿主页面。
218
- *
219
- * 重开会话、断线补数时,地图高亮这类"页面当前状态"必须能恢复——
220
- * 否则刷新一次页面,之前智能体让页面做的事就全丢了。
221
- * 只回放指令:toolCall / message 这些事件如果按历史全量重放,
222
- * 监听器会在挂载瞬间收到几百条陈年事件。
223
- * dispatch 内部按 deliveredCommandKeys 去重,重复调用安全。
224
- */
225
- private replayCommandsFromTurns;
256
+ private replaceFromHistory;
257
+ private invalidateOlderPaging;
258
+ private beginCommandRecovery;
259
+ private scheduleHistoricalCommandRestore;
260
+ private flushBufferedCommands;
261
+ /** 首次订阅前的 REST 历史建立语义事件基线,随后同一工具的订阅回放不再投递。 */
262
+ private seedToolCallDeliveryBaseline;
226
263
  private ensureJoined;
227
264
  private ensureIdleBeforeSend;
228
265
  }
@@ -23,6 +23,8 @@ export interface AgentSessionEvents {
23
23
  toolResult: {
24
24
  toolCall: ToolCallInfo;
25
25
  turn: TurnProjection;
26
+ /** 实时到达,或首次连接/断线重连时由服务端回放。 */
27
+ source: "live" | "bootstrap_replay" | "reconnect_replay";
26
28
  };
27
29
  /** 一轮回复结束。status: completed / paused / interrupted / failed。 */
28
30
  chatEnd: {
@@ -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 实例。多会话共享连接、状态互不干扰。
@@ -27,7 +34,7 @@ export declare class SessionHub {
27
34
  * 只有最后一份归还才退订房间——否则 StrictMode 双跑、路由抖动这类"连两次
28
35
  * 只释放一次"的场景会把仍在用的会话退订掉,之后所有 turn/chat 事件静默丢失。
29
36
  */
30
- connect(sessionId: string): Promise<AgentSession>;
37
+ connect(sessionId: string, options?: SessionConnectOptions): Promise<AgentSession>;
31
38
  /** 已有实例则返回,不触发网络请求。 */
32
39
  peek(sessionId: string): AgentSession | undefined;
33
40
  /** 手动重连(例如登录态变化后)。 */
@@ -1,10 +1,12 @@
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
+ /** 一次 AskUserQuestion 的用户作答(选项选择 + 自定义输入 + 可选补充)。 */
5
5
  export interface AskUserAnswerData {
6
6
  selections: Record<number, number[]>;
7
7
  custom: Record<number, string>;
8
+ /** 选完选项后的自由补充,空着就是没有。 */
9
+ note?: string;
8
10
  }
9
11
  export interface AgentLoopInfo {
10
12
  toolCallId: string;
@@ -43,6 +45,9 @@ export interface SessionState {
43
45
  replay: ReplaySnapshot | null;
44
46
  /** 当前身份对这个会话的权限;"viewer" 表示只读(分享链接打开的)。 */
45
47
  viewerRole: "owner" | "viewer" | null;
48
+ /** Stable anchor for the next older projected page. */
49
+ nextBefore: string | null;
50
+ loadingOlder: boolean;
46
51
  }
47
52
  /** 回放会话的快照视图,由服务端的 replay_state 归一化而来。 */
48
53
  export interface ReplaySnapshot {
@@ -55,6 +60,10 @@ export interface ReplaySnapshot {
55
60
  export declare function toReplaySnapshot(raw: ReplayState | null | undefined): ReplaySnapshot | null;
56
61
  export declare function createInitialSessionState(sessionId: string): SessionState;
57
62
  export declare function extractModeFromBlocks(blocks: ContentBlock[]): "planning" | "executing" | null;
63
+ export declare function reconcileOptimisticUserTurns(currentTurns: TurnProjection[], incomingTurns: TurnProjection[]): TurnProjection[];
64
+ /** Reconcile a bulk authoritative window only by an echoed request identity.
65
+ * Positional fallback is reserved for a single live turn from an older server. */
66
+ export declare function reconcileHistoricalUserTurns(currentTurns: TurnProjection[], incomingTurns: TurnProjection[]): TurnProjection[];
58
67
  /**
59
68
  * 用一份新的 turn 列表重建会话状态。
60
69
  * addErrorMessage 注入的 role==="error" 消息不在 turns 里,默认会被物化过程抹掉;
@@ -62,7 +71,7 @@ export declare function extractModeFromBlocks(blocks: ContentBlock[]): "planning
62
71
  * turn 推进(新消息 / rewind / 重连)时旧错误自动消失。
63
72
  */
64
73
  export declare function withTurns(state: SessionState, turns: TurnProjection[]): SessionState;
65
- export declare function addUserMessage(state: SessionState, content: MessageContent): SessionState;
74
+ export declare function addUserMessage(state: SessionState, content: MessageContent, clientRequestId?: string): SessionState;
66
75
  export declare function upsertTurn(state: SessionState, turn: TurnProjection): SessionState;
67
76
  export declare function applyTurnPatch(state: SessionState, patch: PatchEnvelope): SessionState;
68
77
  export declare function addErrorMessage(state: SessionState, content: string): SessionState;
@@ -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,6 +26,7 @@ export declare class ClientProjectionBuilder {
22
26
  private activeTurns;
23
27
  private lastTurnIds;
24
28
  private latestTurns;
29
+ private turnsById;
25
30
  private loopDescriptions;
26
31
  private pendingChildPauses;
27
32
  private pendingMemoryRefs;
@@ -37,6 +42,7 @@ export declare class ClientProjectionBuilder {
37
42
  seedSequence(maxSeq: number): void;
38
43
  _seedTurns(turns: TurnProjection[]): void;
39
44
  private onMemoryInject;
45
+ private onContextEntry;
40
46
  private onLoopTurn;
41
47
  private onModeChange;
42
48
  private onPlanStatusUpdate;
@@ -49,7 +55,10 @@ export declare class ClientProjectionBuilder {
49
55
  private onPostChatFollowup;
50
56
  private onDelta;
51
57
  private onToolCallCreated;
58
+ private onToolCallArgumentsDelta;
52
59
  private onResponseDone;
60
+ private applyFirstTokenDelivery;
61
+ private applyStreamTiming;
53
62
  private onToolResultDone;
54
63
  private onToolResultDelta;
55
64
  private onToolUi;
@@ -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,7 +1,7 @@
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
7
  } | null): void;
@@ -4,6 +4,7 @@ export interface RawHistoryEntry {
4
4
  kind?: string;
5
5
  loop_name?: string;
6
6
  message?: Record<string, unknown>;
7
+ source?: Record<string, unknown>;
7
8
  data?: Record<string, unknown>;
8
9
  timestamp?: string;
9
10
  [key: string]: unknown;
@@ -1,3 +1,4 @@
1
- export { acceptedPostChatFollowupCompletesLatestRun, ClientProjectionBuilder, type RawEvent, type ProjectionUpdate, } from "./builder";
1
+ export { acceptedPostChatFollowupCompletesLatestRun, acknowledgeTurnEvents, ClientProjectionBuilder, hasChatRunEvent, type RawEvent, type ProjectionUpdate, } from "./builder";
2
2
  export { type TurnState, createTurnState, snapshot, PAUSE_TOOL_NAMES } from "./state";
3
3
  export { projectHistory, type RawHistoryEntry } from "./history";
4
+ export { buildContextTurn } from "./context";
@@ -10,6 +10,9 @@ export interface TurnState {
10
10
  toolCalls: ToolCallProjection[];
11
11
  usage: Record<string, unknown> | null;
12
12
  responseDone: boolean;
13
+ firstTokenMs: number | null;
14
+ streamDurationMs: number | null;
15
+ firstTokenDeliveryMs: number | null;
13
16
  memoryRefs: MemoryRef[] | null;
14
17
  parentForkToolCallId: string | null;
15
18
  }