@blade-hq/agent-client 2610.0.0-beta.51 → 2610.0.0-beta.53
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 +27 -2
- package/dist/blade-client.d.ts +6 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +290 -54
- package/dist/index.js.map +1 -1
- package/dist/resources/site-visits.d.ts +32 -0
- package/dist/rest.d.ts +8 -0
- package/dist/session/agent-session.d.ts +35 -11
- package/dist/session/queue.d.ts +92 -0
- package/dist/session/state.d.ts +8 -0
- package/dist/types/socket-events.d.ts +152 -2
- package/package.json +1 -1
- package/public-api.md +133 -5
package/README.md
CHANGED
|
@@ -397,6 +397,7 @@ chat.getState()
|
|
|
397
397
|
// connection, 连接状态:"connected" | "connecting" | "reconnecting" | "disconnected"
|
|
398
398
|
// errorMessage, 最近一次运行错误
|
|
399
399
|
// replay, 回放状态:{ isReplay, speed, sourceSessionId } | null
|
|
400
|
+
// queue, 等待消息队列快照:{ session_id, revision, paused, pause_reason, items }
|
|
400
401
|
// viewerRole, "owner" | "viewer" | null(只读身份改不动会话)
|
|
401
402
|
// nextBefore, loadingOlder, 历史分页游标与加载状态
|
|
402
403
|
// turns, askAnswers, agentLoops, activeCompaction 进阶字段
|
|
@@ -412,8 +413,8 @@ const unsubscribe = chat.subscribe(() => rerender(chat.getState()))
|
|
|
412
413
|
```ts
|
|
413
414
|
const accepted = await chat.send("你好") // 服务端接受后为 true;拒绝时为 false
|
|
414
415
|
await chat.send("换个方案", { mode: "planning" }) // 指定模式/模型等选项
|
|
415
|
-
chat.
|
|
416
|
-
await chat.stop() //
|
|
416
|
+
await chat.queueMessage("补充:预算不超过 5 万") // 运行中入队;返回服务端确认结果与冲突原因
|
|
417
|
+
await chat.stop() // 停止当前回复(同时暂停队列,需 resumeQueue 继续)
|
|
417
418
|
await chat.compact() // 手动压缩上下文
|
|
418
419
|
if (chat.hasOlderHistory) await chat.loadOlderHistory()
|
|
419
420
|
chat.dispose() // 彻底释放(什么时候该调见下方说明)
|
|
@@ -423,6 +424,28 @@ chat.dispose() // 彻底释放(什么时候
|
|
|
423
424
|
> 1. 用户明确关闭或删除了这个会话,之后不会再回来;
|
|
424
425
|
> 2. 页面长期开着并且会不断创建一次性会话(比如批量任务面板),需要主动回收。
|
|
425
426
|
|
|
427
|
+
### 等待消息队列
|
|
428
|
+
|
|
429
|
+
队列由服务端持久化:运行中发送、暂停期间发送都进入队尾,按顺序逐条执行;关掉页面不影响已确认的入队,重新打开恢复真实状态。等待消息**不会**提前伪装成聊天记录——它只在队列区域展示,真正被接收后才进入消息流。
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
const result = await chat.queueMessage("顺便看下这个报错") // 入队(运行中排队 / 暂停期间照常入队)
|
|
433
|
+
// result: { ok, conflict, snapshot, message }
|
|
434
|
+
|
|
435
|
+
await chat.updateQueuedMessage(item.id, item.version, "改好的正文")
|
|
436
|
+
await chat.reorderQueuedMessages(queue.revision, pendingIdsInNewOrder) // ordered_ids 必须是当前 pending 条目 id 的完整集合
|
|
437
|
+
await chat.cancelQueuedMessage(item.id, item.version)
|
|
438
|
+
await chat.deliverQueuedMessage(item.id, item.version) // 立即补充:交给当前运行在下一个接收点消费
|
|
439
|
+
await chat.resumeQueue() // 暂停后继续执行
|
|
440
|
+
await chat.refreshQueue() // 手动拉一份权威快照(首次连接与每次重连已自动拉取)
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
`chat.getState().queue` 是 `SessionQueueSnapshot`:`{ session_id, revision, paused, pause_reason, items }`。它只会被 revision 更新的快照替换——乱序到达的旧广播不会把新状态顶回去。`items` 只包含 `pending`(待执行)与 `delivering`(等待接收)。
|
|
444
|
+
|
|
445
|
+
- **冲突处理**:`conflict` 表示乐观并发或状态校验失败,服务端快照已自动刷新到本地,此时保留用户草稿并说明原因即可;`ok=false` 且 `snapshot` 为 null(例如鉴权失败)时不要改动本地队列。`queueMessage` 结果未知(ack 超时)时会用同一个 `client_request_id` 重试一次,不会制造第二条。
|
|
446
|
+
- **可操作性判定**:`canEditQueuedMessage` / `canCancelQueuedMessage` / `canDeliverQueuedMessage` 只对 `pending` 返回 true;文案用 `queuedMessageStatusLabel` 与 `queuePauseReasonLabel`。
|
|
447
|
+
- **自己处理广播**:需要绕过状态机时用 `isSessionQueueSnapshot` + `shouldApplySnapshot` + `EMPTY_SESSION_QUEUE`;解析自定义 ack 用 `parseQueueAck`,返回统一的 `QueueOperationResult`(`QueueAck` 是 ack 的线格式)。
|
|
448
|
+
|
|
426
449
|
### 页面协作:让智能体和你的页面互动
|
|
427
450
|
|
|
428
451
|
**页面状态与业务后端 → 智能体**:
|
|
@@ -684,9 +707,11 @@ HTML 一律判无效);`isInternalStatusToolUiCard` 识别内部「阶段进
|
|
|
684
707
|
- **声明式会话**:`SessionDefinition`、`SolutionDefinition`、`SkillDefinition`、`SessionConfig`、`TextFile`、`SessionSetupError`、`SessionSetupStage`
|
|
685
708
|
- **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
|
|
686
709
|
- **聊天项目资源(REST)**:`ChatProjectsResource`、`ChatProject`(含 `delete` 永久删除)
|
|
710
|
+
- **全站访问统计(REST)**:`SiteVisitsResource`、`SiteVisitsSummary`(`client.siteVisits.record()` 上报一次页面访问、`client.siteVisits.summary()` 只读快照)。这两个操作走独立匿名传输:不带 bearer、不触发 401 登录刷新,只依赖匿名访客 Cookie,供内置 Web 同源使用。
|
|
687
711
|
- **会话资源(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`
|
|
688
712
|
- **会话回放**:`ReplayState`、`ReplaySpeed`、`ReplayPreview`、`ReplaySnapshot`、`toReplaySnapshot`、`DEFAULT_REPLAY_SPEED`
|
|
689
713
|
- **会话状态机**:`SessionHub`、`SessionConnectOptions`、`SessionState`、`SendOptions`、`ConnectionStatus`、`AskUserAnswerData`、`AgentLoopInfo`、`ActiveCompactionState`、`createInitialSessionState`、`AgentSessionEventName`
|
|
714
|
+
- **会话消息队列**:`SessionQueueSnapshot`、`QueuedMessage`、`QueuedMessageStatus`、`QueueAck`、`QueueOperationResult`、`EMPTY_SESSION_QUEUE`、`isSessionQueueSnapshot`、`shouldApplySnapshot`、`parseQueueAck`、`canEditQueuedMessage`、`canCancelQueuedMessage`、`canDeliverQueuedMessage`、`queuedMessageStatusLabel`、`queuePauseReasonLabel`
|
|
690
715
|
- **页面协作**:`EmbeddedChat`、`EmbeddedChatOptions`、`CommandHandler`、`CommandEnvelope`、`InboundAction`、`InboundEnvelope`、`isCommandEnvelope`、`isInboundEnvelope`
|
|
691
716
|
- **消息与投影协议**:`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`
|
|
692
717
|
- **MCP Apps 留档与卡片判定**:`McpAppData`、`isMcpAppData`、`McpAppContextState`、`McpAppContextUpdate`、`ToolUiCard`、`ToolUiCardContentType`、`InlineToolUiCardEntry`、`PreviewToolUiCardEntry`、`isToolUiCard`、`resolveToolUiCardContent`、`buildToolUiCardKey`、`isInternalStatusToolUiCard`、`isAppDevToolUiCard`、`collectInlineToolUiCards`、`collectPreviewToolUiCards`(`McpAppContextState` / `McpAppContextUpdate` 配合 `sessions.getMcpAppContext` / `sessions.updateMcpAppContext`:App 保存的状态在下一个新运行才进入模型上下文)
|
package/dist/blade-client.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { ComputersResource } from "./resources/computers";
|
|
|
6
6
|
import { ModelsResource } from "./resources/models";
|
|
7
7
|
import { SessionsResource } from "./resources/sessions";
|
|
8
8
|
import { ChatProjectsResource } from "./resources/chat-projects";
|
|
9
|
+
import { SiteVisitsResource } from "./resources/site-visits";
|
|
9
10
|
import { SessionHub } from "./session/hub";
|
|
10
11
|
import { type TypedSocket } from "./socket";
|
|
11
12
|
export interface BladeClientOptions {
|
|
@@ -39,6 +40,11 @@ export declare class BladeClient {
|
|
|
39
40
|
readonly models: ModelsResource;
|
|
40
41
|
readonly sessions: SessionsResource;
|
|
41
42
|
readonly chatProjects: ChatProjectsResource;
|
|
43
|
+
/**
|
|
44
|
+
* 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
|
|
45
|
+
* 只有内置 Web 的同源页面使用,第三方宿主页面不会被自动统计。
|
|
46
|
+
*/
|
|
47
|
+
readonly siteVisits: SiteVisitsResource;
|
|
42
48
|
/** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
|
|
43
49
|
readonly hub: SessionHub;
|
|
44
50
|
constructor(options: BladeClientOptions);
|
|
@@ -87,7 +93,6 @@ export declare class BladeClient {
|
|
|
87
93
|
private buildHeaders;
|
|
88
94
|
private buildUrl;
|
|
89
95
|
private baseUrlWithTrailingSlash;
|
|
90
|
-
private toBaseRelativePath;
|
|
91
96
|
private isSameBackendUrl;
|
|
92
97
|
private shouldRefreshFor401;
|
|
93
98
|
private hasExplicitBearerToken;
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export { SessionSetupError } from "./session/definition";
|
|
|
17
17
|
export type { SessionConfig, SessionDefinition, SessionSetupStage, SkillDefinition, TextFile, SolutionDefinition, } from "./session/definition";
|
|
18
18
|
export { createInitialSessionState, reconcileHistoricalUserTurns, reconcileOptimisticUserTurns, toReplaySnapshot, } from "./session/state";
|
|
19
19
|
export type { AskUserAnswerData, AgentLoopInfo, ActiveCompactionState, ConnectionStatus, ReplaySnapshot, SessionState, } from "./session/state";
|
|
20
|
+
export { EMPTY_SESSION_QUEUE, canCancelQueuedMessage, canDeliverQueuedMessage, canEditQueuedMessage, isSessionQueueSnapshot, parseQueueAck, queuePauseReasonLabel, queuedMessageStatusLabel, shouldApplySnapshot, } from "./session/queue";
|
|
21
|
+
export type { QueueAck, QueueOperationResult, QueuedMessage, QueuedMessageStatus, SessionQueueSnapshot, } from "./session/queue";
|
|
20
22
|
export { connectEmbedded } from "./commands/embedded";
|
|
21
23
|
export type { EmbeddedChat, EmbeddedChatOptions } from "./commands/embedded";
|
|
22
24
|
export type { CommandHandler } from "./commands/registry";
|
|
@@ -34,6 +36,7 @@ export type { SessionPlugin, SessionPluginActivation } from "./resources/session
|
|
|
34
36
|
export type { SessionPluginConfigError, SessionPluginConfigSet, SessionPluginConfigState, SessionPluginConfigStateName, SessionPluginConfigSummary, SessionPluginConfigUpdate, } from "./resources/sessions";
|
|
35
37
|
export { ChatProjectsResource } from "./resources/chat-projects";
|
|
36
38
|
export type { ChatProject } from "./resources/chat-projects";
|
|
39
|
+
export type { SiteVisitsResource, SiteVisitsSummary } from "./resources/site-visits";
|
|
37
40
|
export { hasChatRunEvent } from "./shared/projection";
|
|
38
41
|
export type { CreateSessionRequest, AppCliAttachment, AppCliDefinition, FileEntry, ImportSessionOptions, PaginatedSessionsResult, GlobalSearchConversationResult, GlobalSearchFileResult, GlobalSearchResult, GlobalSearchResultItem, ResultFeedback, ResultFeedbackReason, HistoricalCommand, SessionContextStats, SessionHistory, SessionTurnsPage, ShareLinkResult, UploadFileEntry, UploadFilesOptions, } from "./resources/sessions";
|
|
39
42
|
export type { ArchivedFileInfo, ArchivedToolCallInfo, ChatMessage, CompactionInfo, FileContentPart, ImageUrlContentPart, MemoryRefInfo, MessageContent, MessageContentPart, TextContentPart, ToolBridgeContent, ToolCallInfo, } from "./schemas/message";
|
package/dist/index.js
CHANGED
|
@@ -167,6 +167,10 @@ function decorateAuthError(error) {
|
|
|
167
167
|
}
|
|
168
168
|
return error;
|
|
169
169
|
}
|
|
170
|
+
function buildBackendUrl(baseUrl, path) {
|
|
171
|
+
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
172
|
+
return new URL(path.startsWith("/") ? path.slice(1) : path, base);
|
|
173
|
+
}
|
|
170
174
|
|
|
171
175
|
// src/resources/auth.ts
|
|
172
176
|
var AuthResource = class {
|
|
@@ -3131,6 +3135,43 @@ var ChatProjectsResource = class {
|
|
|
3131
3135
|
}
|
|
3132
3136
|
};
|
|
3133
3137
|
|
|
3138
|
+
// src/resources/site-visits.ts
|
|
3139
|
+
var SITE_VISITS_PATH = "/api/site-visits";
|
|
3140
|
+
var REQUEST_TIMEOUT_MS = 5e3;
|
|
3141
|
+
var SiteVisitsResource = class {
|
|
3142
|
+
constructor(client) {
|
|
3143
|
+
this.client = client;
|
|
3144
|
+
}
|
|
3145
|
+
client;
|
|
3146
|
+
/** 上报一次页面访问,返回当前快照;不承诺本次已计入或已落盘。 */
|
|
3147
|
+
record() {
|
|
3148
|
+
return this.request("POST");
|
|
3149
|
+
}
|
|
3150
|
+
/** 只读快照:不设置访客 Cookie,也不增加任何计数。 */
|
|
3151
|
+
summary() {
|
|
3152
|
+
return this.request("GET");
|
|
3153
|
+
}
|
|
3154
|
+
async request(method) {
|
|
3155
|
+
const url = buildBackendUrl(this.client.options.baseUrl, SITE_VISITS_PATH);
|
|
3156
|
+
const controller = new AbortController();
|
|
3157
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
3158
|
+
try {
|
|
3159
|
+
const response = await (this.client.options.fetchImpl ?? fetch)(url.toString(), {
|
|
3160
|
+
method,
|
|
3161
|
+
// 访客 Cookie 是唯一的去重依据,必须随请求往返。
|
|
3162
|
+
credentials: "include",
|
|
3163
|
+
signal: controller.signal
|
|
3164
|
+
});
|
|
3165
|
+
if (!response.ok) {
|
|
3166
|
+
throw new BladeApiError(response, await extractErrorDetail(response));
|
|
3167
|
+
}
|
|
3168
|
+
return await response.json();
|
|
3169
|
+
} finally {
|
|
3170
|
+
clearTimeout(timeout);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
};
|
|
3174
|
+
|
|
3134
3175
|
// src/shared/auth-busy.ts
|
|
3135
3176
|
var DEFAULT_RETRY_AFTER_SECONDS = 1;
|
|
3136
3177
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
@@ -3310,6 +3351,123 @@ function replaceWindow(current, page, liveRevisionAtStart, liveRevisions) {
|
|
|
3310
3351
|
]);
|
|
3311
3352
|
}
|
|
3312
3353
|
|
|
3354
|
+
// src/session/queue.ts
|
|
3355
|
+
var EMPTY_SESSION_QUEUE = {
|
|
3356
|
+
session_id: "",
|
|
3357
|
+
revision: 0,
|
|
3358
|
+
paused: false,
|
|
3359
|
+
pause_reason: null,
|
|
3360
|
+
items: []
|
|
3361
|
+
};
|
|
3362
|
+
var QUEUE_FAILURE_MESSAGE = "\u961F\u5217\u64CD\u4F5C\u5931\u8D25";
|
|
3363
|
+
var QUEUED_MESSAGE_STATUSES = [
|
|
3364
|
+
"pending",
|
|
3365
|
+
"delivering",
|
|
3366
|
+
"claimed",
|
|
3367
|
+
"consumed",
|
|
3368
|
+
"cancelled"
|
|
3369
|
+
];
|
|
3370
|
+
function toQueuedMessage(value) {
|
|
3371
|
+
if (!isRecord(value)) return null;
|
|
3372
|
+
const status = typeof value.status === "string" && QUEUED_MESSAGE_STATUSES.includes(value.status) ? value.status : null;
|
|
3373
|
+
if (typeof value.id !== "string" || typeof value.content !== "string" || typeof value.position !== "number" || typeof value.version !== "number" || typeof value.created_at !== "string" || typeof value.updated_at !== "string" || status === null) {
|
|
3374
|
+
return null;
|
|
3375
|
+
}
|
|
3376
|
+
const item = {
|
|
3377
|
+
id: value.id,
|
|
3378
|
+
content: value.content,
|
|
3379
|
+
position: value.position,
|
|
3380
|
+
version: value.version,
|
|
3381
|
+
status,
|
|
3382
|
+
created_at: value.created_at,
|
|
3383
|
+
updated_at: value.updated_at
|
|
3384
|
+
};
|
|
3385
|
+
if (typeof value.target_run_id === "string") item.target_run_id = value.target_run_id;
|
|
3386
|
+
if (typeof value.error === "string") item.error = value.error;
|
|
3387
|
+
return item;
|
|
3388
|
+
}
|
|
3389
|
+
function toQueueSnapshot(value, fallbackSessionId) {
|
|
3390
|
+
if (!isRecord(value)) return null;
|
|
3391
|
+
if (typeof value.revision !== "number" || !Number.isInteger(value.revision) || value.revision < 0) {
|
|
3392
|
+
return null;
|
|
3393
|
+
}
|
|
3394
|
+
if (typeof value.paused !== "boolean") return null;
|
|
3395
|
+
if (!Array.isArray(value.items)) return null;
|
|
3396
|
+
const pauseReason = value.pause_reason;
|
|
3397
|
+
if (pauseReason !== null && pauseReason !== void 0 && typeof pauseReason !== "string") {
|
|
3398
|
+
return null;
|
|
3399
|
+
}
|
|
3400
|
+
const items = [];
|
|
3401
|
+
for (const raw of value.items) {
|
|
3402
|
+
const item = toQueuedMessage(raw);
|
|
3403
|
+
if (!item) return null;
|
|
3404
|
+
items.push(item);
|
|
3405
|
+
}
|
|
3406
|
+
return {
|
|
3407
|
+
session_id: typeof value.session_id === "string" && value.session_id ? value.session_id : fallbackSessionId,
|
|
3408
|
+
revision: value.revision,
|
|
3409
|
+
paused: value.paused,
|
|
3410
|
+
pause_reason: typeof pauseReason === "string" ? pauseReason : null,
|
|
3411
|
+
items
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
function isSessionQueueSnapshot(value) {
|
|
3415
|
+
if (!isRecord(value) || typeof value.session_id !== "string") return false;
|
|
3416
|
+
return toQueueSnapshot(value, value.session_id) !== null;
|
|
3417
|
+
}
|
|
3418
|
+
function shouldApplySnapshot(current, next) {
|
|
3419
|
+
if (!isSessionQueueSnapshot(next)) return false;
|
|
3420
|
+
if (current?.session_id && next.session_id !== current.session_id) return false;
|
|
3421
|
+
return next.revision > (current?.revision ?? -1);
|
|
3422
|
+
}
|
|
3423
|
+
function parseQueueAck(response, fallbackSessionId) {
|
|
3424
|
+
if (!isRecord(response)) {
|
|
3425
|
+
return { ok: false, conflict: false, snapshot: null, message: QUEUE_FAILURE_MESSAGE };
|
|
3426
|
+
}
|
|
3427
|
+
const message = typeof response.message === "string" && response.message ? response.message : null;
|
|
3428
|
+
const status = response.status;
|
|
3429
|
+
if (status !== "ok" && status !== "conflict") {
|
|
3430
|
+
return { ok: false, conflict: false, snapshot: null, message: message ?? QUEUE_FAILURE_MESSAGE };
|
|
3431
|
+
}
|
|
3432
|
+
const snapshot2 = toQueueSnapshot(response, fallbackSessionId);
|
|
3433
|
+
if (status === "conflict") {
|
|
3434
|
+
return { ok: false, conflict: true, snapshot: snapshot2, message: message ?? QUEUE_FAILURE_MESSAGE };
|
|
3435
|
+
}
|
|
3436
|
+
if (!snapshot2) {
|
|
3437
|
+
return { ok: false, conflict: false, snapshot: null, message: message ?? QUEUE_FAILURE_MESSAGE };
|
|
3438
|
+
}
|
|
3439
|
+
return { ok: true, conflict: false, snapshot: snapshot2, message };
|
|
3440
|
+
}
|
|
3441
|
+
function canEditQueuedMessage(item) {
|
|
3442
|
+
return item.status === "pending";
|
|
3443
|
+
}
|
|
3444
|
+
function canCancelQueuedMessage(item) {
|
|
3445
|
+
return item.status === "pending";
|
|
3446
|
+
}
|
|
3447
|
+
function canDeliverQueuedMessage(item) {
|
|
3448
|
+
return item.status === "pending";
|
|
3449
|
+
}
|
|
3450
|
+
var QUEUED_MESSAGE_STATUS_LABELS = {
|
|
3451
|
+
pending: "\u5F85\u6267\u884C",
|
|
3452
|
+
delivering: "\u7B49\u5F85\u63A5\u6536",
|
|
3453
|
+
claimed: "",
|
|
3454
|
+
consumed: "",
|
|
3455
|
+
cancelled: ""
|
|
3456
|
+
};
|
|
3457
|
+
function queuedMessageStatusLabel(status) {
|
|
3458
|
+
return QUEUED_MESSAGE_STATUS_LABELS[status] ?? "";
|
|
3459
|
+
}
|
|
3460
|
+
var QUEUE_PAUSE_REASON_LABELS = {
|
|
3461
|
+
user_stop: "\u5DF2\u6682\u505C",
|
|
3462
|
+
run_failed: "\u4EFB\u52A1\u5F02\u5E38\u5DF2\u6682\u505C",
|
|
3463
|
+
run_unknown: "\u4EFB\u52A1\u7ED3\u675F\u72B6\u6001\u672A\u77E5\uFF0C\u5DF2\u6682\u505C",
|
|
3464
|
+
server_restart: "\u670D\u52A1\u91CD\u542F\u5DF2\u6682\u505C"
|
|
3465
|
+
};
|
|
3466
|
+
function queuePauseReasonLabel(reason) {
|
|
3467
|
+
if (!reason) return null;
|
|
3468
|
+
return QUEUE_PAUSE_REASON_LABELS[reason] ?? reason;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3313
3471
|
// src/schemas/mcp-app.ts
|
|
3314
3472
|
function isMcpAppData(value) {
|
|
3315
3473
|
return isRecord(value) && typeof value.sourceId === "string" && value.sourceId.length > 0 && typeof value.resourceUri === "string" && value.resourceUri.startsWith("ui://") && typeof value.mimeType === "string" && isRecord(value.resourceMeta) && isRecord(value.toolInput) && isRecord(value.toolResult);
|
|
@@ -3608,6 +3766,7 @@ function createInitialSessionState(sessionId) {
|
|
|
3608
3766
|
agentLoops: {},
|
|
3609
3767
|
activeCompaction: null,
|
|
3610
3768
|
replay: null,
|
|
3769
|
+
queue: EMPTY_SESSION_QUEUE,
|
|
3611
3770
|
viewerRole: null,
|
|
3612
3771
|
nextBefore: null,
|
|
3613
3772
|
loadingOlder: false
|
|
@@ -4015,6 +4174,10 @@ function withTurns(state, turns) {
|
|
|
4015
4174
|
status: isWaitingForInput ? "waiting_for_input" : state.status
|
|
4016
4175
|
};
|
|
4017
4176
|
}
|
|
4177
|
+
function withQueue(state, snapshot2) {
|
|
4178
|
+
if (!shouldApplySnapshot(state.queue, snapshot2)) return state;
|
|
4179
|
+
return { ...state, queue: snapshot2 };
|
|
4180
|
+
}
|
|
4018
4181
|
var localTurnCounter = 0;
|
|
4019
4182
|
function addUserMessage(state, content, clientRequestId) {
|
|
4020
4183
|
localTurnCounter += 1;
|
|
@@ -4092,6 +4255,7 @@ function markStreamingTurns(state, turnStatus, toolCallStatus) {
|
|
|
4092
4255
|
var OOM_MESSAGE = "\u6C99\u76D2\u5185\u5B58\u4F7F\u7528\u8D85\u51FA\u9650\u5236\uFF0C\u5DF2\u81EA\u52A8\u91CD\u542F\u3002\u5982\u679C\u7ECF\u5E38\u89E6\u53D1\uFF0C\u53EF\u4EE5\u8054\u7CFB\u7BA1\u7406\u5458\u8C03\u6574\u989D\u5EA6\u3002";
|
|
4093
4256
|
var COMMAND_RESTORE_RETRY_BASE_MS = 1e3;
|
|
4094
4257
|
var COMMAND_RESTORE_RETRY_MAX_MS = 3e4;
|
|
4258
|
+
var QUEUE_ACK_TIMEOUT_MS = 1e4;
|
|
4095
4259
|
var fallbackRequestCounter = 0;
|
|
4096
4260
|
function createClientRequestId() {
|
|
4097
4261
|
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
@@ -4138,6 +4302,14 @@ function isToolBridgeContent(value) {
|
|
|
4138
4302
|
function isCursorInvalidError(error) {
|
|
4139
4303
|
return typeof error === "object" && error !== null && "status" in error && error.status === 409;
|
|
4140
4304
|
}
|
|
4305
|
+
function queueRequestFailure(error) {
|
|
4306
|
+
return {
|
|
4307
|
+
ok: false,
|
|
4308
|
+
conflict: false,
|
|
4309
|
+
snapshot: null,
|
|
4310
|
+
message: error instanceof Error && error.message ? error.message : "\u961F\u5217\u64CD\u4F5C\u5931\u8D25"
|
|
4311
|
+
};
|
|
4312
|
+
}
|
|
4141
4313
|
function commandDeliveryKey(deliveryId) {
|
|
4142
4314
|
return `tool-command:${deliveryId}`;
|
|
4143
4315
|
}
|
|
@@ -4180,8 +4352,6 @@ var AgentSession = class _AgentSession {
|
|
|
4180
4352
|
modeRevision = 0;
|
|
4181
4353
|
liveRevisions = /* @__PURE__ */ new Map();
|
|
4182
4354
|
pendingOptimisticTurnIds = /* @__PURE__ */ new Set();
|
|
4183
|
-
/** Consecutive supplements are persisted as one authoritative user turn. */
|
|
4184
|
-
pendingAppendTurnId = null;
|
|
4185
4355
|
olderPending = null;
|
|
4186
4356
|
replaceInFlight = 0;
|
|
4187
4357
|
// 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
|
|
@@ -4354,21 +4524,95 @@ var AgentSession = class _AgentSession {
|
|
|
4354
4524
|
this.sendPending = false;
|
|
4355
4525
|
}
|
|
4356
4526
|
}
|
|
4357
|
-
/**
|
|
4358
|
-
|
|
4527
|
+
/**
|
|
4528
|
+
* 入队一条等待消息(运行中排队 / 暂停期间照常入队)。
|
|
4529
|
+
*
|
|
4530
|
+
* ack 超时或传输失败时结果未知:用**同一个** `client_request_id` 重试一次,
|
|
4531
|
+
* 服务端按 `(session_id, client_request_id)` 幂等,不会制造第二条。
|
|
4532
|
+
*/
|
|
4533
|
+
async queueMessage(content) {
|
|
4359
4534
|
const text = content.trim();
|
|
4360
|
-
if (!text)
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4535
|
+
if (!text) {
|
|
4536
|
+
return { ok: false, conflict: false, snapshot: null, message: "\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
4537
|
+
}
|
|
4538
|
+
const payload = {
|
|
4539
|
+
session_id: this.sessionId,
|
|
4540
|
+
message: text,
|
|
4541
|
+
client_request_id: createClientRequestId()
|
|
4542
|
+
};
|
|
4543
|
+
try {
|
|
4544
|
+
return await this.requestQueueAck("chat:append", payload);
|
|
4545
|
+
} catch {
|
|
4546
|
+
try {
|
|
4547
|
+
return await this.requestQueueAck("chat:append", payload);
|
|
4548
|
+
} catch (error) {
|
|
4549
|
+
return queueRequestFailure(error);
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4364
4552
|
}
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
return
|
|
4553
|
+
/** 修改一条等待消息的正文;`expectedVersion` 是客户端看到的条目版本。 */
|
|
4554
|
+
async updateQueuedMessage(itemId, expectedVersion, content) {
|
|
4555
|
+
return this.runQueueCommand("chat:queue:update", {
|
|
4556
|
+
session_id: this.sessionId,
|
|
4557
|
+
item_id: itemId,
|
|
4558
|
+
expected_version: expectedVersion,
|
|
4559
|
+
message: content
|
|
4560
|
+
});
|
|
4368
4561
|
}
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4562
|
+
/**
|
|
4563
|
+
* 重排等待消息。`orderedIds` 必须是当前 pending 条目 id 的**完整集合**(顺序任意),
|
|
4564
|
+
* 服务端按 `expectedRevision` 校验后整体替换顺序。
|
|
4565
|
+
*/
|
|
4566
|
+
async reorderQueuedMessages(expectedRevision, orderedIds) {
|
|
4567
|
+
return this.runQueueCommand("chat:queue:reorder", {
|
|
4568
|
+
session_id: this.sessionId,
|
|
4569
|
+
expected_revision: expectedRevision,
|
|
4570
|
+
ordered_ids: orderedIds
|
|
4571
|
+
});
|
|
4572
|
+
}
|
|
4573
|
+
/** 删除一条等待消息;不传版本时由服务端自行判定。 */
|
|
4574
|
+
async cancelQueuedMessage(itemId, expectedVersion) {
|
|
4575
|
+
return this.runQueueCommand("chat:queue:cancel", {
|
|
4576
|
+
session_id: this.sessionId,
|
|
4577
|
+
item_id: itemId,
|
|
4578
|
+
expected_version: expectedVersion
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
/** 立即补充:把等待消息交给当前运行,在下一个接收点消费,不停止也不另起一轮。 */
|
|
4582
|
+
async deliverQueuedMessage(itemId, expectedVersion) {
|
|
4583
|
+
return this.runQueueCommand("chat:queue:deliver", {
|
|
4584
|
+
session_id: this.sessionId,
|
|
4585
|
+
item_id: itemId,
|
|
4586
|
+
expected_version: expectedVersion
|
|
4587
|
+
});
|
|
4588
|
+
}
|
|
4589
|
+
/** 继续执行:清除暂停,并按当前顺序恢复逐条接续。 */
|
|
4590
|
+
async resumeQueue() {
|
|
4591
|
+
return this.runQueueCommand("chat:queue:resume", { session_id: this.sessionId });
|
|
4592
|
+
}
|
|
4593
|
+
/** 拉取一份权威队列快照。首次连接与每次重连后由实例自动调用,也可以手动刷新。 */
|
|
4594
|
+
async refreshQueue() {
|
|
4595
|
+
return this.runQueueCommand("chat:queue:list", { session_id: this.sessionId });
|
|
4596
|
+
}
|
|
4597
|
+
/**
|
|
4598
|
+
* 发出一条队列命令并安装 ack 里的快照。
|
|
4599
|
+
* 传输层失败(超时 / 断线)会 reject,由调用方决定是否重试。
|
|
4600
|
+
*/
|
|
4601
|
+
async requestQueueAck(event, payload) {
|
|
4602
|
+
this.runtime.ensureConnected();
|
|
4603
|
+
const response = await this.runtime.emitWithAck(event, payload, QUEUE_ACK_TIMEOUT_MS);
|
|
4604
|
+
const result = parseQueueAck(response, this.sessionId);
|
|
4605
|
+
const snapshot2 = result.snapshot;
|
|
4606
|
+
if (snapshot2) this.update((state) => withQueue(state, snapshot2));
|
|
4607
|
+
return result;
|
|
4608
|
+
}
|
|
4609
|
+
/** 队列命令的对外入口:传输失败只体现为失败结果,不向调用方抛异常。 */
|
|
4610
|
+
async runQueueCommand(event, payload) {
|
|
4611
|
+
try {
|
|
4612
|
+
return await this.requestQueueAck(event, payload);
|
|
4613
|
+
} catch (error) {
|
|
4614
|
+
return queueRequestFailure(error);
|
|
4615
|
+
}
|
|
4372
4616
|
}
|
|
4373
4617
|
async sendUnlocked(content, options) {
|
|
4374
4618
|
await this.ensureIdleBeforeSend();
|
|
@@ -4494,40 +4738,7 @@ var AgentSession = class _AgentSession {
|
|
|
4494
4738
|
if (optimisticTurnId) {
|
|
4495
4739
|
this.pendingOptimisticTurnIds.delete(optimisticTurnId);
|
|
4496
4740
|
this.liveRevisions.delete(optimisticTurnId);
|
|
4497
|
-
if (this.pendingAppendTurnId === optimisticTurnId) this.pendingAppendTurnId = null;
|
|
4498
|
-
}
|
|
4499
|
-
}
|
|
4500
|
-
/** 智能体运行期间追加补充说明(不打断当前回复)。 */
|
|
4501
|
-
append(text) {
|
|
4502
|
-
this.runtime.ensureConnected();
|
|
4503
|
-
const existingAppendId = this.pendingAppendTurnId;
|
|
4504
|
-
let optimisticTurnId = existingAppendId;
|
|
4505
|
-
this.update((state) => {
|
|
4506
|
-
const existing = existingAppendId ? state.turns.find((turn) => turn.turn_id === existingAppendId) : void 0;
|
|
4507
|
-
if (existing && existing.role === "user") {
|
|
4508
|
-
const blocks = existing.blocks.map(
|
|
4509
|
-
(block, index) => index === 0 && block.type === "text" ? { ...block, content: `${block.content}
|
|
4510
|
-
${text}` } : block
|
|
4511
|
-
);
|
|
4512
|
-
return withTurns(state, state.turns.map(
|
|
4513
|
-
(turn) => turn.turn_id === existingAppendId ? { ...turn, blocks } : turn
|
|
4514
|
-
));
|
|
4515
|
-
}
|
|
4516
|
-
const next = addUserMessage(state, text, createClientRequestId());
|
|
4517
|
-
optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
|
|
4518
|
-
return next;
|
|
4519
|
-
});
|
|
4520
|
-
if (optimisticTurnId) {
|
|
4521
|
-
this.pendingAppendTurnId = optimisticTurnId;
|
|
4522
|
-
if (!this.pendingOptimisticTurnIds.has(optimisticTurnId)) {
|
|
4523
|
-
this.markOptimisticTurnPending(optimisticTurnId);
|
|
4524
|
-
}
|
|
4525
4741
|
}
|
|
4526
|
-
this.runtime.emitEvent("chat:append", {
|
|
4527
|
-
session_id: this.sessionId,
|
|
4528
|
-
message: text,
|
|
4529
|
-
...optimisticTurnId ? { client_request_id: optimisticTurnId.slice("local-user-".length) } : {}
|
|
4530
|
-
});
|
|
4531
4742
|
}
|
|
4532
4743
|
/** 停止当前回复。 */
|
|
4533
4744
|
stop() {
|
|
@@ -4621,6 +4832,15 @@ ${text}` } : block
|
|
|
4621
4832
|
if (this.state.connection === connection) return;
|
|
4622
4833
|
this.update((s) => ({ ...s, connection }));
|
|
4623
4834
|
}
|
|
4835
|
+
/**
|
|
4836
|
+
* @internal 服务端广播的权威队列快照。
|
|
4837
|
+
* 结构不合法或 revision 不新的一律丢弃,绝不用旧快照覆盖新状态。
|
|
4838
|
+
*/
|
|
4839
|
+
_handleQueueUpdated(payload) {
|
|
4840
|
+
if (!isSessionQueueSnapshot(payload)) return;
|
|
4841
|
+
const snapshot2 = payload;
|
|
4842
|
+
this.update((state) => withQueue(state, snapshot2));
|
|
4843
|
+
}
|
|
4624
4844
|
/** @internal socket 重连后由 Hub 调用:重新加入房间并补数。 */
|
|
4625
4845
|
_rejoinAfterReconnect() {
|
|
4626
4846
|
this.timelineEpoch += 1;
|
|
@@ -4663,7 +4883,7 @@ ${text}` } : block
|
|
|
4663
4883
|
this.subscriptionReplaySource = null;
|
|
4664
4884
|
}
|
|
4665
4885
|
this.scheduleHistoricalCommandRestore(commandRecoveryGeneration);
|
|
4666
|
-
await this.syncReplayFromSession();
|
|
4886
|
+
await Promise.all([this.syncReplayFromSession(), this.refreshQueue()]);
|
|
4667
4887
|
});
|
|
4668
4888
|
}
|
|
4669
4889
|
/** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
|
|
@@ -5507,6 +5727,7 @@ var SessionHub = class {
|
|
|
5507
5727
|
if (isReconnect) {
|
|
5508
5728
|
for (const session of this.sessions.values()) {
|
|
5509
5729
|
void session._rejoinAfterReconnect();
|
|
5730
|
+
void session.refreshQueue();
|
|
5510
5731
|
}
|
|
5511
5732
|
}
|
|
5512
5733
|
});
|
|
@@ -5568,6 +5789,9 @@ var SessionHub = class {
|
|
|
5568
5789
|
socket.on("system:error", (data) => {
|
|
5569
5790
|
for (const session of this.route(data.session_id)) session._handleSystemError(data.message);
|
|
5570
5791
|
});
|
|
5792
|
+
socket.on("chat:queue:updated", (data) => {
|
|
5793
|
+
for (const session of this.route(data?.session_id)) session._handleQueueUpdated(data);
|
|
5794
|
+
});
|
|
5571
5795
|
socket.on(
|
|
5572
5796
|
"system:notification",
|
|
5573
5797
|
(data) => {
|
|
@@ -5711,6 +5935,11 @@ var BladeClient = class {
|
|
|
5711
5935
|
models;
|
|
5712
5936
|
sessions;
|
|
5713
5937
|
chatProjects;
|
|
5938
|
+
/**
|
|
5939
|
+
* 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
|
|
5940
|
+
* 只有内置 Web 的同源页面使用,第三方宿主页面不会被自动统计。
|
|
5941
|
+
*/
|
|
5942
|
+
siteVisits;
|
|
5714
5943
|
/** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
|
|
5715
5944
|
hub;
|
|
5716
5945
|
constructor(options) {
|
|
@@ -5724,6 +5953,7 @@ var BladeClient = class {
|
|
|
5724
5953
|
this.models = new ModelsResource(this);
|
|
5725
5954
|
this.sessions = new SessionsResource(this);
|
|
5726
5955
|
this.chatProjects = new ChatProjectsResource(this);
|
|
5956
|
+
this.siteVisits = new SiteVisitsResource(this);
|
|
5727
5957
|
this.hub = new SessionHub(this);
|
|
5728
5958
|
if (this.options.token === void 0) {
|
|
5729
5959
|
this.runtimeToken = readStoredToken(this.options.baseUrl);
|
|
@@ -5894,7 +6124,7 @@ var BladeClient = class {
|
|
|
5894
6124
|
return response;
|
|
5895
6125
|
}
|
|
5896
6126
|
buildAuthedUrl(path) {
|
|
5897
|
-
const url =
|
|
6127
|
+
const url = buildBackendUrl(this.options.baseUrl, path);
|
|
5898
6128
|
const token = this.resolveTokenForUrl(url);
|
|
5899
6129
|
if (token) {
|
|
5900
6130
|
url.searchParams.set("token", token);
|
|
@@ -5913,14 +6143,11 @@ var BladeClient = class {
|
|
|
5913
6143
|
return result;
|
|
5914
6144
|
}
|
|
5915
6145
|
buildUrl(path) {
|
|
5916
|
-
return
|
|
6146
|
+
return buildBackendUrl(this.options.baseUrl, path);
|
|
5917
6147
|
}
|
|
5918
6148
|
baseUrlWithTrailingSlash() {
|
|
5919
6149
|
return this.options.baseUrl.endsWith("/") ? this.options.baseUrl : `${this.options.baseUrl}/`;
|
|
5920
6150
|
}
|
|
5921
|
-
toBaseRelativePath(path) {
|
|
5922
|
-
return path.startsWith("/") ? path.slice(1) : path;
|
|
5923
|
-
}
|
|
5924
6151
|
isSameBackendUrl(url) {
|
|
5925
6152
|
const base = new URL(this.baseUrlWithTrailingSlash());
|
|
5926
6153
|
if (url.origin !== base.origin) {
|
|
@@ -6194,7 +6421,7 @@ function resolveServiceUrl(endpoints, name, path = "") {
|
|
|
6194
6421
|
|
|
6195
6422
|
// src/version.ts
|
|
6196
6423
|
var SDK_NAME = "agent-client";
|
|
6197
|
-
var SDK_VERSION = true ? "2610.0.0-beta.
|
|
6424
|
+
var SDK_VERSION = true ? "2610.0.0-beta.53" : "1.1.1";
|
|
6198
6425
|
|
|
6199
6426
|
// src/commands/protocol.ts
|
|
6200
6427
|
function isCommandEnvelope(value) {
|
|
@@ -6304,6 +6531,7 @@ export {
|
|
|
6304
6531
|
ComputersResource,
|
|
6305
6532
|
DEFAULT_REPLAY_SPEED,
|
|
6306
6533
|
EMPTY_PLATFORM_ENDPOINTS,
|
|
6534
|
+
EMPTY_SESSION_QUEUE,
|
|
6307
6535
|
LayoutType,
|
|
6308
6536
|
ModelsResource,
|
|
6309
6537
|
PollingBackoff,
|
|
@@ -6320,6 +6548,9 @@ export {
|
|
|
6320
6548
|
authBusyRetryDelayMs,
|
|
6321
6549
|
buildMessageContent,
|
|
6322
6550
|
buildToolUiCardKey,
|
|
6551
|
+
canCancelQueuedMessage,
|
|
6552
|
+
canDeliverQueuedMessage,
|
|
6553
|
+
canEditQueuedMessage,
|
|
6323
6554
|
canToggleComputer,
|
|
6324
6555
|
chatErrorForDisplay,
|
|
6325
6556
|
collectInlineToolUiCards,
|
|
@@ -6348,16 +6579,21 @@ export {
|
|
|
6348
6579
|
isInboundEnvelope,
|
|
6349
6580
|
isInternalStatusToolUiCard,
|
|
6350
6581
|
isMcpAppData,
|
|
6582
|
+
isSessionQueueSnapshot,
|
|
6351
6583
|
isToolUiCard,
|
|
6352
6584
|
latestPostChatFollowup,
|
|
6353
6585
|
loadPlatformEndpoints,
|
|
6354
6586
|
normalizeMessageContent,
|
|
6587
|
+
parseQueueAck,
|
|
6355
6588
|
prependOlder,
|
|
6589
|
+
queuePauseReasonLabel,
|
|
6590
|
+
queuedMessageStatusLabel,
|
|
6356
6591
|
reconcileHistoricalUserTurns,
|
|
6357
6592
|
reconcileOptimisticUserTurns,
|
|
6358
6593
|
replaceWindow,
|
|
6359
6594
|
resolveServiceUrl,
|
|
6360
6595
|
resolveToolUiCardContent,
|
|
6596
|
+
shouldApplySnapshot,
|
|
6361
6597
|
sortComputers,
|
|
6362
6598
|
toReplaySnapshot,
|
|
6363
6599
|
transformSlashCommand
|