@blade-hq/agent-client 2610.0.0-beta.7 → 2610.0.0-beta.70
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 +235 -8
- package/dist/auth.d.ts +0 -1
- package/dist/blade-client.d.ts +13 -3
- package/dist/index.d.ts +30 -4
- package/dist/index.js +2036 -266
- package/dist/index.js.map +1 -1
- package/dist/platform-endpoints.d.ts +14 -0
- package/dist/polling-backoff.d.ts +11 -0
- package/dist/resources/chat-groups.d.ts +26 -0
- package/dist/resources/computers.d.ts +115 -0
- package/dist/resources/models.d.ts +2 -0
- package/dist/resources/plugins.d.ts +43 -0
- package/dist/resources/sessions.d.ts +156 -1
- package/dist/resources/site-visits.d.ts +32 -0
- package/dist/rest.d.ts +8 -0
- package/dist/schemas/context.d.ts +49 -0
- package/dist/schemas/event.d.ts +9 -0
- package/dist/schemas/mcp-app.d.ts +30 -0
- package/dist/schemas/message-utils.d.ts +5 -1
- package/dist/schemas/message.d.ts +3 -0
- package/dist/schemas/projection.d.ts +37 -2
- package/dist/schemas/session.d.ts +9 -1
- package/dist/session/agent-session.d.ts +91 -18
- package/dist/session/events.d.ts +4 -0
- package/dist/session/history-page.d.ts +9 -0
- package/dist/session/hub.d.ts +9 -1
- package/dist/session/queue.d.ts +99 -0
- package/dist/session/state.d.ts +18 -2
- package/dist/shared/auth-busy.d.ts +9 -0
- package/dist/shared/projection/builder.d.ts +12 -0
- package/dist/shared/projection/context.d.ts +3 -0
- package/dist/shared/projection/helpers.d.ts +2 -2
- package/dist/shared/projection/history.d.ts +1 -0
- package/dist/shared/projection/index.d.ts +2 -1
- package/dist/shared/projection/state.d.ts +7 -0
- package/dist/shared/tool-ui-card.d.ts +58 -0
- package/dist/shared/type-guards.d.ts +2 -0
- package/dist/types/socket-events.d.ts +195 -6
- package/package.json +1 -1
- package/public-api.md +741 -12
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 {
|
|
@@ -214,6 +218,11 @@ var AuthResource = class {
|
|
|
214
218
|
}
|
|
215
219
|
};
|
|
216
220
|
|
|
221
|
+
// src/shared/type-guards.ts
|
|
222
|
+
function isRecord(value) {
|
|
223
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
224
|
+
}
|
|
225
|
+
|
|
217
226
|
// src/resources/headless.ts
|
|
218
227
|
var HeadlessError = class extends Error {
|
|
219
228
|
constructor(message, detail) {
|
|
@@ -352,8 +361,7 @@ var HeadlessResource = class {
|
|
|
352
361
|
socket.on("system:error", onSystemError);
|
|
353
362
|
socket.emit("session:subscribe", {
|
|
354
363
|
session_id,
|
|
355
|
-
event_format: "raw"
|
|
356
|
-
stream_tokens: this.client.options.streamTokens !== false
|
|
364
|
+
event_format: "raw"
|
|
357
365
|
});
|
|
358
366
|
socket.emit(
|
|
359
367
|
"chat:send",
|
|
@@ -395,9 +403,6 @@ function errorMessageFromResult(result) {
|
|
|
395
403
|
}
|
|
396
404
|
return null;
|
|
397
405
|
}
|
|
398
|
-
function isRecord(value) {
|
|
399
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
400
|
-
}
|
|
401
406
|
function ensureSocketConnected(socket, timeoutMs, detail) {
|
|
402
407
|
if (socket.connected) {
|
|
403
408
|
return Promise.resolve();
|
|
@@ -425,6 +430,81 @@ function ensureSocketConnected(socket, timeoutMs, detail) {
|
|
|
425
430
|
});
|
|
426
431
|
}
|
|
427
432
|
|
|
433
|
+
// src/resources/computers.ts
|
|
434
|
+
function computerState(computer) {
|
|
435
|
+
if (computer.is_primary) return "primary";
|
|
436
|
+
if (computer.enabled) return "enabled";
|
|
437
|
+
return computer.online ? "idle" : "offline";
|
|
438
|
+
}
|
|
439
|
+
function computerOS(computer) {
|
|
440
|
+
const os = (computer.os ?? "").toLowerCase();
|
|
441
|
+
if (os.startsWith("darwin") || os.startsWith("mac")) return "macos";
|
|
442
|
+
if (os.startsWith("windows")) return "windows";
|
|
443
|
+
if (os.startsWith("linux")) return "linux";
|
|
444
|
+
return "unknown";
|
|
445
|
+
}
|
|
446
|
+
function computerPlatformLabel(computer) {
|
|
447
|
+
const os = (computer.os ?? "").trim();
|
|
448
|
+
const arch = (computer.arch ?? "").trim();
|
|
449
|
+
if (os && arch) return `${os}/${arch}`;
|
|
450
|
+
return os || arch || "";
|
|
451
|
+
}
|
|
452
|
+
function computerDaemonVersion(computer) {
|
|
453
|
+
return (computer.daemon_version ?? "").trim();
|
|
454
|
+
}
|
|
455
|
+
function canToggleComputer(computer) {
|
|
456
|
+
return !computer.is_primary;
|
|
457
|
+
}
|
|
458
|
+
function sortComputers(computers) {
|
|
459
|
+
return [...computers].sort((a, b) => {
|
|
460
|
+
const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
|
|
461
|
+
if (byCreated !== 0) return byCreated;
|
|
462
|
+
return a.label.localeCompare(b.label);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
var ComputersResource = class {
|
|
466
|
+
constructor(client) {
|
|
467
|
+
this.client = client;
|
|
468
|
+
}
|
|
469
|
+
client;
|
|
470
|
+
/**
|
|
471
|
+
* 列出当前账号登记过的全部电脑及其在线状态。
|
|
472
|
+
*
|
|
473
|
+
* 首页还没有会话,也要能看到同一份清单——判定各写一份必然分叉,一边的兜底会把
|
|
474
|
+
* 另一边的错误盖住(#1597)。所以账号事实只从这一个接口读。
|
|
475
|
+
*/
|
|
476
|
+
async listAccount() {
|
|
477
|
+
const data = await this.client.json("GET", "/api/daemon/instances");
|
|
478
|
+
return {
|
|
479
|
+
computers: (data.instances ?? []).map((instance) => ({
|
|
480
|
+
...instance,
|
|
481
|
+
online: Boolean(instance.online)
|
|
482
|
+
}))
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
|
|
487
|
+
* 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
|
|
488
|
+
*/
|
|
489
|
+
list(sessionId) {
|
|
490
|
+
return this.client.json(
|
|
491
|
+
"GET",
|
|
492
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
|
|
497
|
+
* 电脑离线时同样能登记,等它上线就直接可用。
|
|
498
|
+
*/
|
|
499
|
+
setEnabled(sessionId, computer, enabled) {
|
|
500
|
+
return this.client.json(
|
|
501
|
+
"PUT",
|
|
502
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
|
|
503
|
+
{ enabled }
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
428
508
|
// src/resources/models.ts
|
|
429
509
|
var ModelsResource = class {
|
|
430
510
|
constructor(client) {
|
|
@@ -473,7 +553,11 @@ var SessionInfo = type({
|
|
|
473
553
|
"disable_tools?": "string[]",
|
|
474
554
|
"runtime_type?": "string | null",
|
|
475
555
|
"daemon_id?": "string | null",
|
|
556
|
+
"agent_runtime_id?": "string | null",
|
|
476
557
|
"workspace_path?": "string | null",
|
|
558
|
+
"ba_version?": "string | null",
|
|
559
|
+
"sandbox_version?": "string | null",
|
|
560
|
+
"chat_group_id?": "string | null",
|
|
477
561
|
"match?": "unknown"
|
|
478
562
|
});
|
|
479
563
|
|
|
@@ -539,7 +623,7 @@ function upsertToolCall(state, toolCallId, toolName, displayName, args) {
|
|
|
539
623
|
});
|
|
540
624
|
}
|
|
541
625
|
}
|
|
542
|
-
function applyToolResult(state, toolCallId, result, status, durationMs, sourceLoop) {
|
|
626
|
+
function applyToolResult(state, toolCallId, result, status, durationMs, ranInParallel, sourceLoop, startedAt = null) {
|
|
543
627
|
let toolName = "";
|
|
544
628
|
let displayName = "";
|
|
545
629
|
let finalResult = result;
|
|
@@ -551,6 +635,8 @@ function applyToolResult(state, toolCallId, result, status, durationMs, sourceLo
|
|
|
551
635
|
}
|
|
552
636
|
const update = { status, result: finalResult };
|
|
553
637
|
if (durationMs != null) update.duration_ms = durationMs;
|
|
638
|
+
if (ranInParallel != null) update.ran_in_parallel = ranInParallel;
|
|
639
|
+
if (startedAt != null) update.started_at = startedAt;
|
|
554
640
|
state.toolCalls[i] = { ...tc, ...update };
|
|
555
641
|
toolName = tc.tool_name;
|
|
556
642
|
displayName = tc.display_name;
|
|
@@ -732,11 +818,17 @@ function buildCompactionTurn(sequence, compactionId, loopId, status, data) {
|
|
|
732
818
|
summary_preview: summaryPreview,
|
|
733
819
|
summary_full: summaryFull,
|
|
734
820
|
archived_count: archivedCount,
|
|
821
|
+
archived_files: Array.isArray(data.archived_files) ? data.archived_files.filter(
|
|
822
|
+
(item) => typeof item === "object" && item !== null && !Array.isArray(item)
|
|
823
|
+
) : null,
|
|
824
|
+
archived_tool_calls: Array.isArray(data.archived_tool_calls) ? data.archived_tool_calls.filter(
|
|
825
|
+
(item) => typeof item === "object" && item !== null && !Array.isArray(item)
|
|
826
|
+
) : null,
|
|
735
827
|
tokens_before: tokensBefore,
|
|
736
828
|
tokens_after: tokensAfter,
|
|
737
829
|
saved_ratio: savedRatio,
|
|
738
830
|
trigger: asTrigger(data.trigger),
|
|
739
|
-
failure_reason:
|
|
831
|
+
failure_reason: [data.failure_reason, data.fallback_reason, data.reason].find((value) => typeof value === "string" && value.length > 0) ?? null,
|
|
740
832
|
fallback_applied: typeof data.fallback_applied === "boolean" ? data.fallback_applied : null
|
|
741
833
|
};
|
|
742
834
|
}
|
|
@@ -991,6 +1083,12 @@ function createTurnState(turnId, loopId, opts = {}) {
|
|
|
991
1083
|
toolCalls: [],
|
|
992
1084
|
usage: null,
|
|
993
1085
|
responseDone: false,
|
|
1086
|
+
firstTokenMs: null,
|
|
1087
|
+
streamDurationMs: null,
|
|
1088
|
+
firstTokenDeliveryMs: null,
|
|
1089
|
+
modelStartedAt: null,
|
|
1090
|
+
modelDurationMs: null,
|
|
1091
|
+
modelStartedAtReported: false,
|
|
994
1092
|
memoryRefs: opts.memoryRefs ?? null,
|
|
995
1093
|
parentForkToolCallId: null
|
|
996
1094
|
};
|
|
@@ -1008,8 +1106,13 @@ function snapshot(state, sequence, status) {
|
|
|
1008
1106
|
tool_calls: [...state.toolCalls],
|
|
1009
1107
|
model: state.model,
|
|
1010
1108
|
usage: state.usage ? { ...state.usage } : null,
|
|
1011
|
-
duration_ms:
|
|
1012
|
-
|
|
1109
|
+
duration_ms: state.modelDurationMs,
|
|
1110
|
+
first_token_ms: state.firstTokenMs,
|
|
1111
|
+
stream_duration_ms: state.streamDurationMs,
|
|
1112
|
+
first_token_delivery_ms: state.firstTokenDeliveryMs,
|
|
1113
|
+
started_at: state.modelStartedAt ?? new Date(state.startedAt).toISOString(),
|
|
1114
|
+
// 没有 payload 上报时用的是 loop:turn 的浏览器事件时间,标记出来供瀑布区分。
|
|
1115
|
+
has_reported_timing: state.modelStartedAtReported,
|
|
1013
1116
|
context_window: state.contextWindow,
|
|
1014
1117
|
memory_refs: state.memoryRefs,
|
|
1015
1118
|
parent_fork_tool_call_id: state.parentForkToolCallId
|
|
@@ -1023,10 +1126,130 @@ function toolStatusFromResult(result) {
|
|
|
1023
1126
|
return inferToolStatus(text);
|
|
1024
1127
|
}
|
|
1025
1128
|
|
|
1129
|
+
// src/schemas/context.ts
|
|
1130
|
+
var BUILTIN_CONTEXT_TITLES = {
|
|
1131
|
+
"agent-instructions": "\u9879\u76EE\u8BF4\u660E",
|
|
1132
|
+
"skill-catalog": "\u53EF\u7528\u80FD\u529B",
|
|
1133
|
+
"runtime-context": "\u5F53\u524D\u5DE5\u4F5C\u73AF\u5883",
|
|
1134
|
+
"workspace-context": "\u5F53\u524D\u5DE5\u4F5C\u533A\u73AF\u5883"
|
|
1135
|
+
};
|
|
1136
|
+
var BUILTIN_CONTEXT_DETAILS = {
|
|
1137
|
+
"agent-instructions": "\u8FD9\u4E9B\u8BF4\u660E\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u7406\u89E3\u5F53\u524D\u9879\u76EE\u548C\u5DE5\u4F5C\u8981\u6C42\u3002",
|
|
1138
|
+
"skill-catalog": "\u8FD9\u4E9B\u80FD\u529B\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u9009\u62E9\u5408\u9002\u7684\u65B9\u5F0F\u5B8C\u6210\u4EFB\u52A1\u3002",
|
|
1139
|
+
"runtime-context": "\u8FD9\u4E9B\u4FE1\u606F\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u4E86\u89E3\u5F53\u524D\u6587\u4EF6\u5939\u548C\u8FD0\u884C\u6761\u4EF6\u3002",
|
|
1140
|
+
"workspace-context": "\u8FD9\u4E9B\u4FE1\u606F\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u4E86\u89E3\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u3001\u6587\u4EF6\u548C\u8FD0\u884C\u73AF\u5883\u3002"
|
|
1141
|
+
};
|
|
1142
|
+
function nonEmptyString(value) {
|
|
1143
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1144
|
+
}
|
|
1145
|
+
function contextProjectionData(fields) {
|
|
1146
|
+
const contextKind = nonEmptyString(fields.context_kind);
|
|
1147
|
+
const contextKey = nonEmptyString(fields.context_key);
|
|
1148
|
+
const contextName = nonEmptyString(fields.context_name);
|
|
1149
|
+
const contextRevision = nonEmptyString(fields.context_revision);
|
|
1150
|
+
if (!contextKind || !contextKey || !contextName || !contextRevision || typeof fields.context_order !== "number" || !Number.isFinite(fields.context_order) || !fields.context_action) {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
return {
|
|
1154
|
+
context_kind: contextKind,
|
|
1155
|
+
context_key: contextKey,
|
|
1156
|
+
context_name: contextName,
|
|
1157
|
+
context_order: fields.context_order,
|
|
1158
|
+
context_action: fields.context_action,
|
|
1159
|
+
context_revision: contextRevision,
|
|
1160
|
+
display_title: nonEmptyString(fields.display_title),
|
|
1161
|
+
display_summary: nonEmptyString(fields.display_summary),
|
|
1162
|
+
sources: Array.isArray(fields.sources) ? fields.sources : []
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
function getContextDisplayState(context) {
|
|
1166
|
+
const title = nonEmptyString(context.display_title) ?? BUILTIN_CONTEXT_TITLES[context.context_kind] ?? "\u5BF9\u8BDD\u80CC\u666F";
|
|
1167
|
+
const isRemoved = context.context_action === "removed";
|
|
1168
|
+
const summary = nonEmptyString(context.display_summary) ?? (isRemoved ? "\u5DF2\u505C\u6B62\u7528\u4E8E\u540E\u7EED\u5BF9\u8BDD" : context.context_action === "retained" ? "\u7EE7\u7EED\u7528\u4E8E\u540E\u7EED\u5BF9\u8BDD" : context.context_kind === "agent-instructions" ? "\u5DF2\u66F4\u65B0\u672C\u6B21\u5BF9\u8BDD\u4F7F\u7528\u7684\u9879\u76EE\u8BF4\u660E" : context.context_kind === "skill-catalog" ? "\u5DF2\u66F4\u65B0\u672C\u6B21\u53EF\u7528\u80FD\u529B" : context.context_kind === "runtime-context" ? "\u5DF2\u66F4\u65B0\u672C\u6B21\u5DE5\u4F5C\u73AF\u5883" : context.context_kind === "workspace-context" ? "\u5DF2\u66F4\u65B0\u672C\u6B21\u5DE5\u4F5C\u533A\u73AF\u5883" : "\u5DF2\u66F4\u65B0\u672C\u6B21\u5BF9\u8BDD\u80CC\u666F");
|
|
1169
|
+
const detail = isRemoved ? `${title}\u5DF2\u4E0D\u518D\u7528\u4E8E\u540E\u7EED\u5BF9\u8BDD\u3002` : BUILTIN_CONTEXT_DETAILS[context.context_kind] ?? "\u8FD9\u4E9B\u4FE1\u606F\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u7406\u89E3\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
1170
|
+
return { title, summary, detail, isRemoved };
|
|
1171
|
+
}
|
|
1172
|
+
function groupAdjacentContextRuns(kinds) {
|
|
1173
|
+
const runs = [];
|
|
1174
|
+
let current = null;
|
|
1175
|
+
kinds.forEach((kind, index) => {
|
|
1176
|
+
if (kind === null) {
|
|
1177
|
+
current = null;
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (current === null) {
|
|
1181
|
+
current = [];
|
|
1182
|
+
runs.push(current);
|
|
1183
|
+
}
|
|
1184
|
+
current.push(index);
|
|
1185
|
+
});
|
|
1186
|
+
return runs;
|
|
1187
|
+
}
|
|
1188
|
+
function getContextGroupDisplayState(contexts) {
|
|
1189
|
+
const count = contexts.length;
|
|
1190
|
+
if (count === 1) {
|
|
1191
|
+
const single = getContextDisplayState(contexts[0]);
|
|
1192
|
+
return { title: single.title, summary: single.summary, count };
|
|
1193
|
+
}
|
|
1194
|
+
const removed = contexts.filter(
|
|
1195
|
+
(context) => context.context_action === "removed"
|
|
1196
|
+
).length;
|
|
1197
|
+
const updated = count - removed;
|
|
1198
|
+
const parts = [];
|
|
1199
|
+
if (updated > 0) parts.push(`${updated} \u9879\u5DF2\u66F4\u65B0`);
|
|
1200
|
+
if (removed > 0) parts.push(`${removed} \u9879\u5DF2\u505C\u7528`);
|
|
1201
|
+
return {
|
|
1202
|
+
title: "\u5BF9\u8BDD\u80CC\u666F",
|
|
1203
|
+
summary: parts.join(" \xB7 ") || `${count} \u9879`,
|
|
1204
|
+
count
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/shared/projection/context.ts
|
|
1209
|
+
function buildContextTurn(entry, sequence) {
|
|
1210
|
+
if (entry.kind !== "context" || !isRecord(entry.source) || !isRecord(entry.data)) {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
const display = isRecord(entry.data.display) ? entry.data.display : {};
|
|
1214
|
+
const message = isRecord(entry.message) ? entry.message : {};
|
|
1215
|
+
const context = contextProjectionData({
|
|
1216
|
+
context_kind: typeof entry.source.kind === "string" ? entry.source.kind : null,
|
|
1217
|
+
context_key: typeof entry.source.key === "string" ? entry.source.key : null,
|
|
1218
|
+
context_name: typeof entry.data.contribution_name === "string" ? entry.data.contribution_name : null,
|
|
1219
|
+
context_order: typeof entry.data.order === "number" ? entry.data.order : null,
|
|
1220
|
+
context_action: entry.data.action === "published" || entry.data.action === "retained" || entry.data.action === "removed" ? entry.data.action : null,
|
|
1221
|
+
context_revision: typeof entry.data.revision === "string" ? entry.data.revision : null,
|
|
1222
|
+
display_title: typeof display.title === "string" ? display.title : null,
|
|
1223
|
+
display_summary: typeof display.summary === "string" ? display.summary : null,
|
|
1224
|
+
sources: Array.isArray(display.sources) ? display.sources.filter(isRecord) : []
|
|
1225
|
+
});
|
|
1226
|
+
if (!context) return null;
|
|
1227
|
+
const turnId = typeof entry.id === "string" && entry.id ? entry.id : `context-${sequence}`;
|
|
1228
|
+
return {
|
|
1229
|
+
id: turnId,
|
|
1230
|
+
sequence,
|
|
1231
|
+
turn_id: turnId,
|
|
1232
|
+
loop_id: typeof entry.loop_name === "string" && entry.loop_name ? entry.loop_name : "root",
|
|
1233
|
+
kind: "context",
|
|
1234
|
+
role: "system",
|
|
1235
|
+
status: "completed",
|
|
1236
|
+
blocks: [],
|
|
1237
|
+
tool_calls: [],
|
|
1238
|
+
model: null,
|
|
1239
|
+
usage: null,
|
|
1240
|
+
duration_ms: 0,
|
|
1241
|
+
context_content: Object.hasOwn(message, "content") ? message.content : null,
|
|
1242
|
+
...context
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1026
1246
|
// src/shared/projection/builder.ts
|
|
1247
|
+
function acknowledgeTurnEvents(acknowledge) {
|
|
1248
|
+
acknowledge?.();
|
|
1249
|
+
}
|
|
1027
1250
|
function hasChatRunEvent(events) {
|
|
1028
1251
|
return events.some(
|
|
1029
|
-
(event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
1252
|
+
(event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
1030
1253
|
);
|
|
1031
1254
|
}
|
|
1032
1255
|
function acceptedPostChatFollowupCompletesLatestRun(events, updates) {
|
|
@@ -1058,7 +1281,8 @@ function completedResponseText(content) {
|
|
|
1058
1281
|
).filter((part) => part.type === "text").map((part) => String(part.text ?? "")).join("");
|
|
1059
1282
|
}
|
|
1060
1283
|
function replaceTextContent(state, content) {
|
|
1061
|
-
|
|
1284
|
+
const currentContent = state.blocks.filter((block) => block.type === "text").map((block) => String(block.content ?? "")).join("");
|
|
1285
|
+
if (currentContent === content) return;
|
|
1062
1286
|
const replacement = { type: "text", content };
|
|
1063
1287
|
const blocks = [];
|
|
1064
1288
|
let inserted = false;
|
|
@@ -1078,11 +1302,39 @@ function replaceTextContent(state, content) {
|
|
|
1078
1302
|
}
|
|
1079
1303
|
state.blocks = blocks;
|
|
1080
1304
|
}
|
|
1305
|
+
function applyResponseBlockOrder(state, value) {
|
|
1306
|
+
if (!Array.isArray(value)) return false;
|
|
1307
|
+
const ordered = [];
|
|
1308
|
+
for (const item of value) {
|
|
1309
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
|
|
1310
|
+
const block = item;
|
|
1311
|
+
if (block.type === "text" || block.type === "thinking") {
|
|
1312
|
+
const content = String(block[block.type] ?? "");
|
|
1313
|
+
if (content) ordered.push({ type: block.type, content });
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
if (block.type !== "tool_call") continue;
|
|
1317
|
+
const toolCallId = String(block.id ?? "");
|
|
1318
|
+
const toolBlock = state.blocks.find(
|
|
1319
|
+
(candidate) => candidate.type === "tool_use" && candidate.tool_call_id === toolCallId
|
|
1320
|
+
);
|
|
1321
|
+
if (toolBlock) ordered.push(toolBlock);
|
|
1322
|
+
}
|
|
1323
|
+
if (ordered.length === 0 && value.length > 0) return false;
|
|
1324
|
+
const unrelatedBlocks = state.blocks.filter(
|
|
1325
|
+
(block) => block.type !== "text" && block.type !== "thinking" && block.type !== "tool_use"
|
|
1326
|
+
);
|
|
1327
|
+
state.blocks = [...ordered, ...unrelatedBlocks];
|
|
1328
|
+
return true;
|
|
1329
|
+
}
|
|
1081
1330
|
var ClientProjectionBuilder = class {
|
|
1082
1331
|
activeTurns = /* @__PURE__ */ new Map();
|
|
1083
1332
|
lastTurnIds = /* @__PURE__ */ new Map();
|
|
1084
1333
|
latestTurns = /* @__PURE__ */ new Map();
|
|
1334
|
+
turnsById = /* @__PURE__ */ new Map();
|
|
1085
1335
|
loopDescriptions = /* @__PURE__ */ new Map();
|
|
1336
|
+
/** loop_name → 正在运行的子智能体起点,agent:end 结算并挂到父工具调用上。 */
|
|
1337
|
+
activeSubagentRuns = /* @__PURE__ */ new Map();
|
|
1086
1338
|
pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1087
1339
|
pendingMemoryRefs = /* @__PURE__ */ new Map();
|
|
1088
1340
|
activeCompactions = /* @__PURE__ */ new Map();
|
|
@@ -1117,6 +1369,8 @@ var ClientProjectionBuilder = class {
|
|
|
1117
1369
|
return this.onMemoryInject(loopId, payload);
|
|
1118
1370
|
case "memory:inject:none":
|
|
1119
1371
|
return null;
|
|
1372
|
+
case "context:entry":
|
|
1373
|
+
return this.onContextEntry(payload);
|
|
1120
1374
|
case "loop:turn":
|
|
1121
1375
|
return this.onLoopTurn(loopId, payload);
|
|
1122
1376
|
case "mode:change":
|
|
@@ -1140,6 +1394,25 @@ var ClientProjectionBuilder = class {
|
|
|
1140
1394
|
}
|
|
1141
1395
|
if (event.type.startsWith("compaction:"))
|
|
1142
1396
|
return this.onCompaction(event.type, loopId, payload);
|
|
1397
|
+
if (event.type === "llm:first_token:delivered") {
|
|
1398
|
+
const targetTurnId = String(payload.turn_id ?? "");
|
|
1399
|
+
const active = this.activeTurns.get(loopId);
|
|
1400
|
+
if (active && (!targetTurnId || active.turnId === targetTurnId)) {
|
|
1401
|
+
this.applyFirstTokenDelivery(active, payload);
|
|
1402
|
+
return this.syncPatch(active);
|
|
1403
|
+
}
|
|
1404
|
+
const latest = targetTurnId ? this.turnsById.get(targetTurnId) : this.latestTurns.get(loopId);
|
|
1405
|
+
const duration = payload.first_token_delivery_ms;
|
|
1406
|
+
if (!latest || latest.role !== "assistant" || typeof duration !== "number" || duration < 0) {
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
const turn = { ...latest, first_token_delivery_ms: duration };
|
|
1410
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1411
|
+
if (this.latestTurns.get(loopId)?.turn_id === turn.turn_id) {
|
|
1412
|
+
this.latestTurns.set(loopId, turn);
|
|
1413
|
+
}
|
|
1414
|
+
return [{ kind: "upsert", turn }];
|
|
1415
|
+
}
|
|
1143
1416
|
const state = this.activeTurns.get(loopId);
|
|
1144
1417
|
if (!state) {
|
|
1145
1418
|
if (event.type === "chat:end") {
|
|
@@ -1155,16 +1428,28 @@ var ClientProjectionBuilder = class {
|
|
|
1155
1428
|
state.toolCalls = [];
|
|
1156
1429
|
state.usage = null;
|
|
1157
1430
|
state.responseDone = false;
|
|
1431
|
+
state.firstTokenMs = null;
|
|
1432
|
+
state.streamDurationMs = null;
|
|
1433
|
+
state.firstTokenDeliveryMs = null;
|
|
1158
1434
|
return this.syncPatch(state);
|
|
1159
1435
|
case "llm:thinking:delta":
|
|
1436
|
+
this.applyFirstTokenDelivery(state, payload);
|
|
1160
1437
|
return this.onDelta(state, "thinking", "", String(payload.content ?? ""));
|
|
1161
1438
|
case "llm:content:delta":
|
|
1439
|
+
this.applyFirstTokenDelivery(state, payload);
|
|
1162
1440
|
return this.onDelta(state, "text", "", String(payload.content ?? ""));
|
|
1163
1441
|
case "llm:content:complete":
|
|
1164
1442
|
replaceTextContent(state, String(payload.content ?? ""));
|
|
1165
1443
|
return this.syncPatch(state);
|
|
1444
|
+
case "llm:content:done":
|
|
1445
|
+
this.applyStreamTiming(state, payload);
|
|
1446
|
+
return this.syncPatch(state);
|
|
1166
1447
|
case "llm:tool_call:created":
|
|
1448
|
+
this.applyFirstTokenDelivery(state, payload);
|
|
1167
1449
|
return this.onToolCallCreated(state, payload);
|
|
1450
|
+
case "llm:tool_call:arguments:delta":
|
|
1451
|
+
this.applyFirstTokenDelivery(state, payload);
|
|
1452
|
+
return this.onToolCallArgumentsDelta(state, payload);
|
|
1168
1453
|
case "llm:response:done":
|
|
1169
1454
|
return this.onResponseDone(state, loopId, payload);
|
|
1170
1455
|
case "tool:result:delta":
|
|
@@ -1191,7 +1476,9 @@ var ClientProjectionBuilder = class {
|
|
|
1191
1476
|
this.activeTurns.clear();
|
|
1192
1477
|
this.lastTurnIds.clear();
|
|
1193
1478
|
this.latestTurns.clear();
|
|
1479
|
+
this.turnsById.clear();
|
|
1194
1480
|
this.loopDescriptions.clear();
|
|
1481
|
+
this.activeSubagentRuns.clear();
|
|
1195
1482
|
this.pendingChildPauses.clear();
|
|
1196
1483
|
this.pendingMemoryRefs.clear();
|
|
1197
1484
|
this.activeCompactions.clear();
|
|
@@ -1206,15 +1493,18 @@ var ClientProjectionBuilder = class {
|
|
|
1206
1493
|
}
|
|
1207
1494
|
_seedTurns(turns) {
|
|
1208
1495
|
const latestTurns = /* @__PURE__ */ new Map();
|
|
1496
|
+
const turnsById = /* @__PURE__ */ new Map();
|
|
1209
1497
|
for (const turn of turns) {
|
|
1210
1498
|
this.seedSequence(turn.sequence);
|
|
1211
1499
|
if (!isConversationTurn(turn)) continue;
|
|
1500
|
+
turnsById.set(turn.turn_id, turn);
|
|
1212
1501
|
const current = latestTurns.get(turn.loop_id);
|
|
1213
1502
|
if (!current || turn.sequence >= current.sequence) {
|
|
1214
1503
|
latestTurns.set(turn.loop_id, turn);
|
|
1215
1504
|
}
|
|
1216
1505
|
}
|
|
1217
1506
|
this.latestTurns = latestTurns;
|
|
1507
|
+
this.turnsById = turnsById;
|
|
1218
1508
|
}
|
|
1219
1509
|
// --- Event handlers ---
|
|
1220
1510
|
onMemoryInject(loopId, payload) {
|
|
@@ -1232,11 +1522,16 @@ var ClientProjectionBuilder = class {
|
|
|
1232
1522
|
}
|
|
1233
1523
|
return null;
|
|
1234
1524
|
}
|
|
1525
|
+
onContextEntry(payload) {
|
|
1526
|
+
if (!isRecord(payload.entry)) return null;
|
|
1527
|
+
const turn = buildContextTurn(payload.entry, this.nextSeq());
|
|
1528
|
+
return turn ? [{ kind: "upsert", turn }] : null;
|
|
1529
|
+
}
|
|
1235
1530
|
onLoopTurn(loopId, payload) {
|
|
1236
1531
|
let turnId = String(payload.turn_id ?? this.nextTurnId(`turn:${loopId}`));
|
|
1237
1532
|
let memoryRefs = null;
|
|
1238
1533
|
const pending = this.pendingMemoryRefs.get(loopId);
|
|
1239
|
-
if (pending) {
|
|
1534
|
+
if (pending && (!pending.turnId || pending.turnId === turnId)) {
|
|
1240
1535
|
this.pendingMemoryRefs.delete(loopId);
|
|
1241
1536
|
if (pending.turnId) turnId = pending.turnId;
|
|
1242
1537
|
memoryRefs = pending.refs;
|
|
@@ -1252,6 +1547,7 @@ var ClientProjectionBuilder = class {
|
|
|
1252
1547
|
this.lastTurnIds.set(loopId, turnId);
|
|
1253
1548
|
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1254
1549
|
this.latestTurns.set(loopId, turn);
|
|
1550
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1255
1551
|
return [{ kind: "upsert", turn }];
|
|
1256
1552
|
}
|
|
1257
1553
|
onModeChange(loopId, payload) {
|
|
@@ -1288,11 +1584,11 @@ var ClientProjectionBuilder = class {
|
|
|
1288
1584
|
loopId,
|
|
1289
1585
|
toolCallId: optStr(payload.tool_call_id) ?? void 0,
|
|
1290
1586
|
toolName: toolNameFromPayload(payload) ?? void 0,
|
|
1291
|
-
workspacePath:
|
|
1587
|
+
workspacePath: isRecord(payload.workspace_change) ? optStr(payload.workspace_change.to) ?? void 0 : void 0
|
|
1292
1588
|
}
|
|
1293
1589
|
];
|
|
1294
1590
|
const change = payload.workspace_change;
|
|
1295
|
-
if (
|
|
1591
|
+
if (isRecord(change)) {
|
|
1296
1592
|
const source = optStr(change.source) ?? "";
|
|
1297
1593
|
const projectName = optStr(change.project_name) ?? "";
|
|
1298
1594
|
const fromPath = optStr(change.from) ?? "";
|
|
@@ -1324,6 +1620,13 @@ var ClientProjectionBuilder = class {
|
|
|
1324
1620
|
onAgentStart(loopId, payload) {
|
|
1325
1621
|
const description = optStr(payload.description);
|
|
1326
1622
|
if (description) this.loopDescriptions.set(loopId, description);
|
|
1623
|
+
const subagentStartedAt = optStr(payload.started_at);
|
|
1624
|
+
if (subagentStartedAt) {
|
|
1625
|
+
this.activeSubagentRuns.set(loopId, {
|
|
1626
|
+
runId: optStr(payload.run_id) ?? loopId,
|
|
1627
|
+
startedAt: subagentStartedAt
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1327
1630
|
const turn = buildSystemNotificationTurn(
|
|
1328
1631
|
this.nextSeq(),
|
|
1329
1632
|
this.nextTurnId("agent:start"),
|
|
@@ -1385,7 +1688,32 @@ var ClientProjectionBuilder = class {
|
|
|
1385
1688
|
}
|
|
1386
1689
|
const directTurn = this.activeTurns.get(loopId) ?? this.latestTurns.get(loopId);
|
|
1387
1690
|
setToolCallStatus(directTurn, toolCallId, "done", null, null);
|
|
1388
|
-
|
|
1691
|
+
const updates = this.collectLoopUpdates(loopId, parentLoopId);
|
|
1692
|
+
const entryId = typeof payload.entry_id === "string" ? payload.entry_id : "";
|
|
1693
|
+
if (!entryId) return updates;
|
|
1694
|
+
const answerData = isRecord(payload.answer_data) ? payload.answer_data : {};
|
|
1695
|
+
const answerTurn = {
|
|
1696
|
+
id: entryId,
|
|
1697
|
+
sequence: this.nextSeq(),
|
|
1698
|
+
turn_id: entryId,
|
|
1699
|
+
loop_id: loopId,
|
|
1700
|
+
kind: "message",
|
|
1701
|
+
role: "assistant",
|
|
1702
|
+
status: "completed",
|
|
1703
|
+
blocks: [
|
|
1704
|
+
{
|
|
1705
|
+
type: "ask_user_answer",
|
|
1706
|
+
content: answerData,
|
|
1707
|
+
tool_call_id: toolCallId || null
|
|
1708
|
+
}
|
|
1709
|
+
],
|
|
1710
|
+
tool_calls: [],
|
|
1711
|
+
model: null,
|
|
1712
|
+
usage: null,
|
|
1713
|
+
duration_ms: 0,
|
|
1714
|
+
client_request_id: typeof payload.client_request_id === "string" ? payload.client_request_id : null
|
|
1715
|
+
};
|
|
1716
|
+
return [...updates, { kind: "upsert", turn: answerTurn }];
|
|
1389
1717
|
}
|
|
1390
1718
|
onUserMessage(_loopId, payload) {
|
|
1391
1719
|
const entryId = String(payload.entry_id ?? this.nextTurnId("user"));
|
|
@@ -1402,7 +1730,8 @@ var ClientProjectionBuilder = class {
|
|
|
1402
1730
|
tool_calls: [],
|
|
1403
1731
|
model: null,
|
|
1404
1732
|
usage: null,
|
|
1405
|
-
duration_ms: 0
|
|
1733
|
+
duration_ms: 0,
|
|
1734
|
+
client_request_id: typeof payload.client_request_id === "string" ? payload.client_request_id : null
|
|
1406
1735
|
};
|
|
1407
1736
|
this.latestTurns.set("root", turn);
|
|
1408
1737
|
return [{ kind: "upsert", turn }];
|
|
@@ -1430,13 +1759,7 @@ var ClientProjectionBuilder = class {
|
|
|
1430
1759
|
}
|
|
1431
1760
|
onToolCallCreated(state, payload) {
|
|
1432
1761
|
if ("arguments_delta" in payload) {
|
|
1433
|
-
|
|
1434
|
-
const toolCallId2 = String(payload.id ?? "");
|
|
1435
|
-
if (delta && toolCallId2) {
|
|
1436
|
-
appendToolCallArguments(state, toolCallId2, delta);
|
|
1437
|
-
return this.syncPatch(state);
|
|
1438
|
-
}
|
|
1439
|
-
return null;
|
|
1762
|
+
return this.onToolCallArgumentsDelta(state, payload);
|
|
1440
1763
|
}
|
|
1441
1764
|
const toolCallId = String(payload.id ?? "");
|
|
1442
1765
|
const toolName = toolNameFromPayload(payload);
|
|
@@ -1445,16 +1768,21 @@ var ClientProjectionBuilder = class {
|
|
|
1445
1768
|
upsertToolCall(state, toolCallId, toolName, displayName, args);
|
|
1446
1769
|
return this.syncPatch(state);
|
|
1447
1770
|
}
|
|
1771
|
+
onToolCallArgumentsDelta(state, payload) {
|
|
1772
|
+
const delta = String(payload.arguments_delta ?? "");
|
|
1773
|
+
const toolCallId = String(payload.id ?? "");
|
|
1774
|
+
if (!delta || !toolCallId) return null;
|
|
1775
|
+
appendToolCallArguments(state, toolCallId, delta);
|
|
1776
|
+
return this.syncPatch(state);
|
|
1777
|
+
}
|
|
1448
1778
|
onResponseDone(state, loopId, payload) {
|
|
1449
1779
|
state.responseDone = true;
|
|
1780
|
+
this.applyStreamTiming(state, payload);
|
|
1450
1781
|
const model = optStr(payload.model);
|
|
1451
1782
|
if (model) state.model = model;
|
|
1452
1783
|
if (typeof payload.usage === "object" && payload.usage !== null) {
|
|
1453
1784
|
state.usage = { ...payload.usage };
|
|
1454
1785
|
}
|
|
1455
|
-
if ("content" in payload) {
|
|
1456
|
-
replaceTextContent(state, completedResponseText(payload.content));
|
|
1457
|
-
}
|
|
1458
1786
|
const toolCalls = payload.tool_calls;
|
|
1459
1787
|
if (Array.isArray(toolCalls)) {
|
|
1460
1788
|
for (const raw of toolCalls) {
|
|
@@ -1472,11 +1800,42 @@ var ClientProjectionBuilder = class {
|
|
|
1472
1800
|
);
|
|
1473
1801
|
}
|
|
1474
1802
|
}
|
|
1475
|
-
if (state.
|
|
1803
|
+
if (!applyResponseBlockOrder(state, payload.response_blocks) && "content" in payload) {
|
|
1804
|
+
replaceTextContent(state, completedResponseText(payload.content));
|
|
1805
|
+
}
|
|
1806
|
+
const isNativeProviderResponse = typeof payload.provider === "string" && payload.provider !== "";
|
|
1807
|
+
if (state.toolCalls.length === 0 || isNativeProviderResponse && state.toolCalls.every(
|
|
1808
|
+
(toolCall) => toolCall.status !== "pending" && toolCall.status !== "awaiting_answer"
|
|
1809
|
+
)) {
|
|
1476
1810
|
return this.finalize(loopId, "completed");
|
|
1477
1811
|
}
|
|
1478
1812
|
return this.syncPatch(state);
|
|
1479
1813
|
}
|
|
1814
|
+
applyFirstTokenDelivery(state, payload) {
|
|
1815
|
+
const firstTokenMs = payload.first_token_ms;
|
|
1816
|
+
if (state.firstTokenMs === null && typeof firstTokenMs === "number" && firstTokenMs >= 0) {
|
|
1817
|
+
state.firstTokenMs = Math.round(firstTokenMs);
|
|
1818
|
+
}
|
|
1819
|
+
const duration = payload.first_token_delivery_ms;
|
|
1820
|
+
if (state.firstTokenDeliveryMs === null && typeof duration === "number" && duration >= 0) {
|
|
1821
|
+
state.firstTokenDeliveryMs = duration;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
applyStreamTiming(state, payload) {
|
|
1825
|
+
if (typeof payload.first_token_ms === "number" && payload.first_token_ms >= 0) {
|
|
1826
|
+
state.firstTokenMs = Math.round(payload.first_token_ms);
|
|
1827
|
+
}
|
|
1828
|
+
if (typeof payload.stream_duration_ms === "number" && payload.stream_duration_ms >= 0) {
|
|
1829
|
+
state.streamDurationMs = Math.round(payload.stream_duration_ms);
|
|
1830
|
+
}
|
|
1831
|
+
if (typeof payload.started_at === "string" && payload.started_at) {
|
|
1832
|
+
state.modelStartedAt = payload.started_at;
|
|
1833
|
+
state.modelStartedAtReported = true;
|
|
1834
|
+
}
|
|
1835
|
+
if (typeof payload.duration_ms === "number" && payload.duration_ms >= 0) {
|
|
1836
|
+
state.modelDurationMs = Math.round(payload.duration_ms);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1480
1839
|
onToolResultDone(state, loopId, payload) {
|
|
1481
1840
|
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1482
1841
|
const streamedResult = state.toolCalls.find((toolCall) => toolCall.id === toolCallId)?.result;
|
|
@@ -1484,9 +1843,12 @@ var ClientProjectionBuilder = class {
|
|
|
1484
1843
|
const status = toolStatusFromResult(content);
|
|
1485
1844
|
const rawDuration = payload.duration_ms;
|
|
1486
1845
|
const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
|
|
1846
|
+
const ranInParallel = typeof payload.ran_in_parallel === "boolean" ? payload.ran_in_parallel : null;
|
|
1487
1847
|
const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
|
|
1488
|
-
|
|
1489
|
-
|
|
1848
|
+
const startedAt = typeof payload.started_at === "string" && payload.started_at ? payload.started_at : null;
|
|
1849
|
+
applyToolResult(state, toolCallId, content, status, durationMs, ranInParallel, sl, startedAt);
|
|
1850
|
+
const isNativeProviderResult = typeof payload.provider === "string" && payload.provider !== "";
|
|
1851
|
+
if (!isNativeProviderResult && this.shouldFinalizeAfterToolResults(state)) {
|
|
1490
1852
|
return this.finalize(loopId, "completed");
|
|
1491
1853
|
}
|
|
1492
1854
|
return this.syncPatch(state);
|
|
@@ -1515,10 +1877,13 @@ var ClientProjectionBuilder = class {
|
|
|
1515
1877
|
onToolBridge(state, payload) {
|
|
1516
1878
|
const bridge = payload.bridge;
|
|
1517
1879
|
if (typeof bridge !== "object" || bridge === null) return null;
|
|
1880
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1881
|
+
if (!toolCallId || !state.toolCalls.some((toolCall) => toolCall.id === toolCallId)) return null;
|
|
1518
1882
|
state.blocks.push({
|
|
1519
1883
|
type: "tool_bridge",
|
|
1520
1884
|
content: bridge,
|
|
1521
|
-
|
|
1885
|
+
delivery_id: String(payload.delivery_id ?? "") || null,
|
|
1886
|
+
tool_call_id: toolCallId
|
|
1522
1887
|
});
|
|
1523
1888
|
return this.syncPatch(state);
|
|
1524
1889
|
}
|
|
@@ -1527,6 +1892,11 @@ var ClientProjectionBuilder = class {
|
|
|
1527
1892
|
const toolName = String(payload.tool_name ?? "");
|
|
1528
1893
|
const displayName = this.displayNameResolver(toolName);
|
|
1529
1894
|
upsertToolCall(state, toolCallId, toolName, displayName, "");
|
|
1895
|
+
const relatedToolCallId = String(payload.related_tool_call_id ?? "");
|
|
1896
|
+
if (relatedToolCallId && relatedToolCallId !== toolCallId) {
|
|
1897
|
+
const related = state.toolCalls.find((toolCall) => toolCall.id === relatedToolCallId);
|
|
1898
|
+
if (related) related.status = "awaiting_answer";
|
|
1899
|
+
}
|
|
1530
1900
|
return this.syncPatch(state);
|
|
1531
1901
|
}
|
|
1532
1902
|
onChildPause(state, loopId, payload) {
|
|
@@ -1632,6 +2002,7 @@ var ClientProjectionBuilder = class {
|
|
|
1632
2002
|
}
|
|
1633
2003
|
onAgentEnd(state, loopId, payload) {
|
|
1634
2004
|
const ok = payload.ok !== false;
|
|
2005
|
+
this.settleSubagentRun(loopId, payload);
|
|
1635
2006
|
const status = ok ? "done" : "error";
|
|
1636
2007
|
const title = `Agent: ${payload.description ?? payload.skill_id ?? "agent"}`;
|
|
1637
2008
|
const detail = optStr(payload.error);
|
|
@@ -1658,6 +2029,32 @@ var ClientProjectionBuilder = class {
|
|
|
1658
2029
|
);
|
|
1659
2030
|
return [{ kind: "upsert", turn }];
|
|
1660
2031
|
}
|
|
2032
|
+
settleSubagentRun(loopId, payload) {
|
|
2033
|
+
const active = this.activeSubagentRuns.get(loopId);
|
|
2034
|
+
this.activeSubagentRuns.delete(loopId);
|
|
2035
|
+
const parentToolCallId = optStr(payload.parent_fork_tool_call_id);
|
|
2036
|
+
if (!parentToolCallId) return;
|
|
2037
|
+
const rawDuration = payload.run_duration_ms;
|
|
2038
|
+
const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
|
|
2039
|
+
const rawStatus = optStr(payload.run_status);
|
|
2040
|
+
const status = rawStatus === "failed" || rawStatus === "cancelled" || rawStatus === "paused" || rawStatus === "incomplete" ? rawStatus : payload.ok === false ? "failed" : "completed";
|
|
2041
|
+
const run = {
|
|
2042
|
+
run_id: active?.runId ?? optStr(payload.run_id) ?? loopId,
|
|
2043
|
+
scope: "subagent",
|
|
2044
|
+
loop_id: loopId,
|
|
2045
|
+
started_at: active?.startedAt ?? optStr(payload.run_started_at),
|
|
2046
|
+
duration_ms: durationMs,
|
|
2047
|
+
status
|
|
2048
|
+
};
|
|
2049
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2050
|
+
for (const turn of [...this.latestTurns.values(), ...this.turnsById.values()]) {
|
|
2051
|
+
if (seen.has(turn)) continue;
|
|
2052
|
+
seen.add(turn);
|
|
2053
|
+
const toolCall = turn.tool_calls.find((item) => item.id === parentToolCallId);
|
|
2054
|
+
if (!toolCall) continue;
|
|
2055
|
+
toolCall.subagent_runs = [...toolCall.subagent_runs ?? [], run];
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
1661
2058
|
dropIncompleteToolCalls(state) {
|
|
1662
2059
|
if (state.responseDone) return;
|
|
1663
2060
|
state.toolCalls = [];
|
|
@@ -1707,6 +2104,7 @@ var ClientProjectionBuilder = class {
|
|
|
1707
2104
|
syncPatch(state) {
|
|
1708
2105
|
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1709
2106
|
this.latestTurns.set(state.loopId, turn);
|
|
2107
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1710
2108
|
this.lastTurnIds.set(state.loopId, turn.turn_id);
|
|
1711
2109
|
return [{ kind: "upsert", turn }];
|
|
1712
2110
|
}
|
|
@@ -1717,6 +2115,7 @@ var ClientProjectionBuilder = class {
|
|
|
1717
2115
|
const turn = snapshot(state, this.nextSeq(), status);
|
|
1718
2116
|
this.lastTurnIds.set(loopId, turn.turn_id);
|
|
1719
2117
|
this.latestTurns.set(loopId, turn);
|
|
2118
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1720
2119
|
return [{ kind: "upsert", turn }];
|
|
1721
2120
|
}
|
|
1722
2121
|
shouldFinalizeAfterToolResults(state) {
|
|
@@ -1754,9 +2153,6 @@ var ClientProjectionBuilder = class {
|
|
|
1754
2153
|
return `${prefix}:${this.syntheticCounter}:${hex}`;
|
|
1755
2154
|
}
|
|
1756
2155
|
};
|
|
1757
|
-
function isRecord2(value) {
|
|
1758
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1759
|
-
}
|
|
1760
2156
|
function optStr(value) {
|
|
1761
2157
|
if (value == null || value === "") return null;
|
|
1762
2158
|
return String(value);
|
|
@@ -1783,6 +2179,8 @@ function projectHistory(entries) {
|
|
|
1783
2179
|
const latestAssistantByLoop = /* @__PURE__ */ new Map();
|
|
1784
2180
|
const pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1785
2181
|
const answeredToolCallIds = /* @__PURE__ */ new Set();
|
|
2182
|
+
const nativeToolTurns = /* @__PURE__ */ new Set();
|
|
2183
|
+
const turnEntryAliases = /* @__PURE__ */ new Map();
|
|
1786
2184
|
let latestRootMessage = null;
|
|
1787
2185
|
let latestRootAssistant = null;
|
|
1788
2186
|
let sequence = 0;
|
|
@@ -1790,7 +2188,40 @@ function projectHistory(entries) {
|
|
|
1790
2188
|
const kind = String(entry.kind ?? "");
|
|
1791
2189
|
const loopId = String(entry.loop_name ?? "root");
|
|
1792
2190
|
const id = String(entry.id ?? `history-${sequence + 1}`);
|
|
1793
|
-
if (kind === "
|
|
2191
|
+
if (kind === "turn_timing" && isRecord(entry.data)) {
|
|
2192
|
+
const turnId = typeof entry.data.turn_id === "string" ? entry.data.turn_id : "";
|
|
2193
|
+
const duration = numberOrNull(entry.data.first_token_delivery_ms);
|
|
2194
|
+
const target = turnEntryAliases.get(turnId);
|
|
2195
|
+
if (target && duration !== null) target.first_token_delivery_ms = duration;
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
if (kind === "run_timing" && isRecord(entry.data)) {
|
|
2199
|
+
const data = entry.data;
|
|
2200
|
+
if (data.scope === "subagent") {
|
|
2201
|
+
const parentToolCallId = String(data.parent_fork_tool_call_id ?? "");
|
|
2202
|
+
const duration = numberOrNull(data.duration_ms);
|
|
2203
|
+
const rawStatus = String(data.status ?? "completed");
|
|
2204
|
+
const status = rawStatus === "failed" || rawStatus === "cancelled" || rawStatus === "paused" || rawStatus === "incomplete" ? rawStatus : "completed";
|
|
2205
|
+
for (const turn of turns) {
|
|
2206
|
+
const toolCall = turn.tool_calls.find((item) => item.id === parentToolCallId);
|
|
2207
|
+
if (!toolCall) continue;
|
|
2208
|
+
toolCall.subagent_runs = [
|
|
2209
|
+
...toolCall.subagent_runs ?? [],
|
|
2210
|
+
{
|
|
2211
|
+
run_id: String(data.run_id ?? ""),
|
|
2212
|
+
scope: "subagent",
|
|
2213
|
+
loop_id: loopId,
|
|
2214
|
+
started_at: typeof data.started_at === "string" ? data.started_at : null,
|
|
2215
|
+
duration_ms: duration,
|
|
2216
|
+
status
|
|
2217
|
+
}
|
|
2218
|
+
];
|
|
2219
|
+
break;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
if (kind === "post_chat_followup" && isRecord(entry.data)) {
|
|
1794
2225
|
const assistantEntryId = String(entry.data.assistant_entry_id ?? "");
|
|
1795
2226
|
if (latestRootMessage?.role === "assistant" && latestRootMessage.turn_id === assistantEntryId) {
|
|
1796
2227
|
const followup = normalizePostChatFollowup(entry.data, assistantEntryId);
|
|
@@ -1798,7 +2229,15 @@ function projectHistory(entries) {
|
|
|
1798
2229
|
}
|
|
1799
2230
|
continue;
|
|
1800
2231
|
}
|
|
1801
|
-
if (kind === "
|
|
2232
|
+
if (kind === "context") {
|
|
2233
|
+
const turn = buildContextTurn(entry, sequence + 1);
|
|
2234
|
+
if (turn) {
|
|
2235
|
+
sequence += 1;
|
|
2236
|
+
turns.push(turn);
|
|
2237
|
+
}
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
if (kind === "message" && isRecord(entry.message)) {
|
|
1802
2241
|
const message = entry.message;
|
|
1803
2242
|
const role = String(message.role ?? "");
|
|
1804
2243
|
if (role === "tool") {
|
|
@@ -1808,6 +2247,12 @@ function projectHistory(entries) {
|
|
|
1808
2247
|
if (owner && toolCall) {
|
|
1809
2248
|
toolCall.status = message._error ? "error" : inferToolStatus(String(message.content ?? ""));
|
|
1810
2249
|
toolCall.result = message.content ?? "";
|
|
2250
|
+
const duration = numberOrNull(message._duration_ms);
|
|
2251
|
+
if (duration !== null) toolCall.duration_ms = duration;
|
|
2252
|
+
if (typeof message._started_at === "string") {
|
|
2253
|
+
toolCall.started_at = message._started_at;
|
|
2254
|
+
}
|
|
2255
|
+
if (message._ran_in_parallel === true) toolCall.ran_in_parallel = true;
|
|
1811
2256
|
owner.blocks.push({
|
|
1812
2257
|
type: "tool_result",
|
|
1813
2258
|
content: message.content ?? "",
|
|
@@ -1817,7 +2262,7 @@ function projectHistory(entries) {
|
|
|
1817
2262
|
continue;
|
|
1818
2263
|
}
|
|
1819
2264
|
if (role !== "user" && role !== "assistant" && role !== "system") continue;
|
|
1820
|
-
const toolCalls = role === "assistant" ? buildToolCalls(message.tool_calls) : [];
|
|
2265
|
+
const toolCalls = role === "assistant" && !message._error ? buildToolCalls(message.tool_calls) : [];
|
|
1821
2266
|
const blocks = buildBlocks(message, toolCalls);
|
|
1822
2267
|
const turn = {
|
|
1823
2268
|
id,
|
|
@@ -1830,14 +2275,52 @@ function projectHistory(entries) {
|
|
|
1830
2275
|
blocks,
|
|
1831
2276
|
tool_calls: toolCalls,
|
|
1832
2277
|
model: stringOrNull(message.model),
|
|
1833
|
-
usage:
|
|
1834
|
-
|
|
1835
|
-
|
|
2278
|
+
usage: isRecord(message._usage) ? { ...message._usage } : null,
|
|
2279
|
+
// 模型请求区间来自持久化字段;旧会话缺失时为 null,不用落盘时刻冒充(#2581 A1)。
|
|
2280
|
+
duration_ms: numberOrNull(message._duration_ms),
|
|
2281
|
+
first_token_ms: numberOrNull(message._first_token_ms),
|
|
2282
|
+
stream_duration_ms: numberOrNull(message._stream_duration_ms),
|
|
2283
|
+
started_at: typeof message._started_at === "string" ? message._started_at : typeof entry.timestamp === "string" ? entry.timestamp : void 0,
|
|
2284
|
+
has_reported_timing: typeof message._started_at === "string",
|
|
2285
|
+
// fork 子会话的父锚点:历史里落在消息级 _parent_fork_tool_call_id。丢掉它,
|
|
2286
|
+
// 刷新后工作流步骤 fork 就配不回 workflow:run 工具调用(普通 Agent fork 有
|
|
2287
|
+
// root Agent 工具调用兜底配对,工作流 fork 没有,子智能体卡会整体消失)。
|
|
2288
|
+
parent_fork_tool_call_id: stringOrNull(message._parent_fork_tool_call_id)
|
|
1836
2289
|
};
|
|
2290
|
+
const isNativeToolEntry = role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.some(
|
|
2291
|
+
(toolCall) => isRecord(toolCall) && typeof toolCall.provider === "string"
|
|
2292
|
+
);
|
|
2293
|
+
const previousTurn = turns[turns.length - 1];
|
|
2294
|
+
if (role === "assistant" && previousTurn?.role === "assistant" && previousTurn.loop_id === loopId && nativeToolTurns.has(previousTurn)) {
|
|
2295
|
+
nativeToolTurns.delete(previousTurn);
|
|
2296
|
+
previousTurn.blocks.push(...turn.blocks);
|
|
2297
|
+
previousTurn.tool_calls.push(...turn.tool_calls);
|
|
2298
|
+
previousTurn.status = turn.status;
|
|
2299
|
+
previousTurn.model = turn.model ?? previousTurn.model;
|
|
2300
|
+
previousTurn.usage = turn.usage ?? previousTurn.usage;
|
|
2301
|
+
if (turn.duration_ms) previousTurn.duration_ms = turn.duration_ms;
|
|
2302
|
+
previousTurn.first_token_ms = turn.first_token_ms ?? previousTurn.first_token_ms;
|
|
2303
|
+
previousTurn.stream_duration_ms = turn.stream_duration_ms ?? previousTurn.stream_duration_ms;
|
|
2304
|
+
if (turn.has_reported_timing) {
|
|
2305
|
+
previousTurn.started_at = turn.started_at;
|
|
2306
|
+
previousTurn.has_reported_timing = true;
|
|
2307
|
+
} else if (!previousTurn.has_reported_timing) {
|
|
2308
|
+
previousTurn.started_at = previousTurn.started_at ?? turn.started_at;
|
|
2309
|
+
}
|
|
2310
|
+
previousTurn.id = turn.id;
|
|
2311
|
+
previousTurn.turn_id = turn.turn_id;
|
|
2312
|
+
if (isNativeToolEntry) nativeToolTurns.add(previousTurn);
|
|
2313
|
+
turnEntryAliases.set(id, previousTurn);
|
|
2314
|
+
if (loopId === "root") latestRootMessage = previousTurn;
|
|
2315
|
+
latestAssistantByLoop.set(loopId, previousTurn);
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
1837
2318
|
if (loopId === "root" && (role === "user" || role === "assistant")) {
|
|
1838
2319
|
latestRootMessage = turn;
|
|
1839
2320
|
}
|
|
1840
2321
|
turns.push(turn);
|
|
2322
|
+
turnEntryAliases.set(id, turn);
|
|
2323
|
+
if (isNativeToolEntry) nativeToolTurns.add(turn);
|
|
1841
2324
|
if (role === "assistant") latestAssistantByLoop.set(loopId, turn);
|
|
1842
2325
|
if (loopId === "root") {
|
|
1843
2326
|
if (role === "assistant") latestRootAssistant = turn;
|
|
@@ -1846,10 +2329,25 @@ function projectHistory(entries) {
|
|
|
1846
2329
|
for (const toolCall of toolCalls) toolOwners.set(toolCall.id, turn);
|
|
1847
2330
|
continue;
|
|
1848
2331
|
}
|
|
2332
|
+
if (kind === "tool_result") {
|
|
2333
|
+
const data = entry.data ?? {};
|
|
2334
|
+
const toolCallId = String(data.tool_call_id ?? "");
|
|
2335
|
+
const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
2336
|
+
if (owner && toolCallId) {
|
|
2337
|
+
const rawContent = data.content;
|
|
2338
|
+
const content = typeof rawContent === "string" ? rawContent : Array.isArray(rawContent) ? rawContent : String(rawContent ?? "");
|
|
2339
|
+
const status = data.is_error === true ? "error" : inferToolStatus(typeof content === "string" ? content : JSON.stringify(content));
|
|
2340
|
+
setToolCallStatus(owner, toolCallId, status, content, null);
|
|
2341
|
+
if (!owner.blocks.some((block) => block.type === "tool_result" && block.tool_call_id === toolCallId)) {
|
|
2342
|
+
owner.blocks.push({ type: "tool_result", content, tool_call_id: toolCallId });
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
1849
2347
|
if (kind === "mode_change") {
|
|
1850
2348
|
turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
|
|
1851
2349
|
} else if (kind === "workspace_change") {
|
|
1852
|
-
const change =
|
|
2350
|
+
const change = isRecord(entry.data) ? entry.data : {};
|
|
1853
2351
|
const source = stringOrNull(change.source) ?? "";
|
|
1854
2352
|
const projectName = stringOrNull(change.project_name) ?? "";
|
|
1855
2353
|
const fromPath = stringOrNull(change.from) ?? "";
|
|
@@ -1878,19 +2376,21 @@ function projectHistory(entries) {
|
|
|
1878
2376
|
} else if (kind === "tool_ui" || kind === "tool_bridge") {
|
|
1879
2377
|
const data = entry.data ?? {};
|
|
1880
2378
|
const toolCallId = String(data.tool_call_id ?? "");
|
|
1881
|
-
const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
1882
|
-
const
|
|
2379
|
+
const owner = kind === "tool_bridge" ? toolCallId ? toolOwners.get(toolCallId) : void 0 : toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
2380
|
+
const rawContent = kind === "tool_ui" ? data.ui : data.bridge;
|
|
2381
|
+
const content = kind === "tool_ui" && isRecord(rawContent) ? { ...rawContent, archived: true } : rawContent;
|
|
1883
2382
|
if (owner && content !== void 0) {
|
|
1884
2383
|
owner.blocks.push({
|
|
1885
2384
|
type: kind,
|
|
1886
2385
|
content,
|
|
2386
|
+
...kind === "tool_bridge" ? { delivery_id: id } : {},
|
|
1887
2387
|
tool_call_id: toolCallId || null
|
|
1888
2388
|
});
|
|
1889
2389
|
}
|
|
1890
2390
|
} else if (kind === "compaction" || kind === "tool_result_archive") {
|
|
1891
2391
|
const data = entry.data ?? {};
|
|
1892
2392
|
const compactionId = String(data.compaction_id ?? entry.id ?? `history-${sequence + 1}`);
|
|
1893
|
-
let content =
|
|
2393
|
+
let content = isRecord(entry.message) ? entry.message.content : void 0;
|
|
1894
2394
|
if (kind === "tool_result_archive") {
|
|
1895
2395
|
const archivedCount = Array.isArray(data.archived_files) ? data.archived_files.length : 0;
|
|
1896
2396
|
content = `<compaction-summary>
|
|
@@ -1898,18 +2398,24 @@ function projectHistory(entries) {
|
|
|
1898
2398
|
</compaction-summary>`;
|
|
1899
2399
|
}
|
|
1900
2400
|
turns.push(
|
|
1901
|
-
buildCompactionTurn(
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2401
|
+
buildCompactionTurn(
|
|
2402
|
+
++sequence,
|
|
2403
|
+
compactionId,
|
|
2404
|
+
loopId,
|
|
2405
|
+
data.status === "failed" ? "failed" : "completed",
|
|
2406
|
+
{
|
|
2407
|
+
...data,
|
|
2408
|
+
content
|
|
2409
|
+
}
|
|
2410
|
+
)
|
|
1905
2411
|
);
|
|
1906
2412
|
} else if (kind === "child_pause") {
|
|
1907
2413
|
const data = entry.data ?? {};
|
|
1908
|
-
const sourceLoop =
|
|
2414
|
+
const sourceLoop = isRecord(data.source_loop) ? data.source_loop : {};
|
|
1909
2415
|
const childLoopId = String(data.child_loop_name ?? sourceLoop.name ?? "");
|
|
1910
2416
|
const childToolCallId = String(data.child_pause_tool_call_id ?? "");
|
|
1911
2417
|
const parentToolCallId = String(data.parent_fork_tool_call_id ?? "");
|
|
1912
|
-
const pauseToolData =
|
|
2418
|
+
const pauseToolData = isRecord(data.pause_tool_data) ? data.pause_tool_data : {};
|
|
1913
2419
|
const description = String(sourceLoop.description ?? "");
|
|
1914
2420
|
applyAskUserPauseToTurn(
|
|
1915
2421
|
latestAssistantByLoop.get(childLoopId),
|
|
@@ -1997,15 +2503,15 @@ function parseToolArguments(value) {
|
|
|
1997
2503
|
if (!value) return {};
|
|
1998
2504
|
try {
|
|
1999
2505
|
const parsed = JSON.parse(value);
|
|
2000
|
-
return
|
|
2506
|
+
return isRecord(parsed) ? parsed : {};
|
|
2001
2507
|
} catch {
|
|
2002
2508
|
return {};
|
|
2003
2509
|
}
|
|
2004
2510
|
}
|
|
2005
2511
|
function buildToolCalls(value) {
|
|
2006
2512
|
if (!Array.isArray(value)) return [];
|
|
2007
|
-
return value.filter(
|
|
2008
|
-
const fn =
|
|
2513
|
+
return value.filter(isRecord).map((raw) => {
|
|
2514
|
+
const fn = isRecord(raw.function) ? raw.function : {};
|
|
2009
2515
|
const name = String(fn.name ?? "");
|
|
2010
2516
|
return {
|
|
2011
2517
|
id: String(raw.id ?? ""),
|
|
@@ -2021,23 +2527,40 @@ function buildBlocks(message, toolCalls) {
|
|
|
2021
2527
|
const displayContent = message.role === "user" ? message._display_content : void 0;
|
|
2022
2528
|
const stored = message._blocks;
|
|
2023
2529
|
const blocks = [];
|
|
2530
|
+
const storedToolCallIds = /* @__PURE__ */ new Set();
|
|
2024
2531
|
if (displayContent !== void 0) {
|
|
2025
2532
|
blocks.push({ type: "text", content: displayContent });
|
|
2026
2533
|
} else if (Array.isArray(stored)) {
|
|
2027
2534
|
for (const raw of stored) {
|
|
2028
|
-
if (!
|
|
2535
|
+
if (!isRecord(raw)) continue;
|
|
2029
2536
|
const type3 = String(raw.type ?? "");
|
|
2030
2537
|
if (type3 === "thinking") blocks.push({ type: type3, content: raw.thinking ?? raw.content ?? "" });
|
|
2031
2538
|
if (type3 === "text") blocks.push({ type: type3, content: raw.text ?? raw.content ?? "" });
|
|
2539
|
+
if (type3 === "tool_call") {
|
|
2540
|
+
const toolCallId = String(raw.id ?? "");
|
|
2541
|
+
const toolCall = toolCalls.find((candidate) => candidate.id === toolCallId);
|
|
2542
|
+
if (!toolCall) continue;
|
|
2543
|
+
storedToolCallIds.add(toolCallId);
|
|
2544
|
+
blocks.push({
|
|
2545
|
+
type: "tool_use",
|
|
2546
|
+
content: toolCall.arguments,
|
|
2547
|
+
tool_call_id: toolCall.id,
|
|
2548
|
+
tool_name: toolCall.tool_name,
|
|
2549
|
+
display_name: toolCall.display_name
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2032
2552
|
}
|
|
2033
2553
|
} else if (message.content !== void 0 && message.content !== "") {
|
|
2034
2554
|
blocks.push({ type: "text", content: message.content });
|
|
2035
2555
|
}
|
|
2036
2556
|
for (const toolCall of toolCalls) {
|
|
2557
|
+
if (storedToolCallIds.has(toolCall.id)) continue;
|
|
2037
2558
|
blocks.push({
|
|
2038
2559
|
type: "tool_use",
|
|
2039
|
-
content:
|
|
2040
|
-
tool_call_id: toolCall.id
|
|
2560
|
+
content: toolCall.arguments,
|
|
2561
|
+
tool_call_id: toolCall.id,
|
|
2562
|
+
tool_name: toolCall.tool_name,
|
|
2563
|
+
display_name: toolCall.display_name
|
|
2041
2564
|
});
|
|
2042
2565
|
}
|
|
2043
2566
|
return blocks;
|
|
@@ -2058,14 +2581,11 @@ function markerTurn(id, sequence, loopId, type3, content, toolCallId = null) {
|
|
|
2058
2581
|
duration_ms: 0
|
|
2059
2582
|
};
|
|
2060
2583
|
}
|
|
2061
|
-
function isRecord3(value) {
|
|
2062
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2063
|
-
}
|
|
2064
2584
|
function stringOrNull(value) {
|
|
2065
2585
|
return typeof value === "string" ? value : null;
|
|
2066
2586
|
}
|
|
2067
|
-
function
|
|
2068
|
-
return typeof value === "number" ? value :
|
|
2587
|
+
function numberOrNull(value) {
|
|
2588
|
+
return typeof value === "number" && value >= 0 ? value : null;
|
|
2069
2589
|
}
|
|
2070
2590
|
function findLatestAssistant(turns, loopId) {
|
|
2071
2591
|
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
@@ -2094,13 +2614,14 @@ function toCreateSessionPayload(request) {
|
|
|
2094
2614
|
solution_snapshot_session_id: request.solution_snapshot_session_id ?? null,
|
|
2095
2615
|
compaction_ratio: request.compaction_ratio ?? null,
|
|
2096
2616
|
...request.runtime_type ? { runtime_type: request.runtime_type } : {},
|
|
2097
|
-
...request.daemon_id ? { daemon_id: request.daemon_id } : {}
|
|
2617
|
+
...request.daemon_id ? { daemon_id: request.daemon_id } : {},
|
|
2618
|
+
...request.agent_runtime_id ? { agent_runtime_id: request.agent_runtime_id } : {},
|
|
2619
|
+
...request.chat_group_id ? { chat_group_id: request.chat_group_id } : {}
|
|
2098
2620
|
};
|
|
2099
2621
|
}
|
|
2100
2622
|
var builtinSolutionIds = /* @__PURE__ */ new Set([
|
|
2101
2623
|
"app-dev",
|
|
2102
2624
|
"general_chat",
|
|
2103
|
-
"multica",
|
|
2104
2625
|
"night_build",
|
|
2105
2626
|
"skill_editor",
|
|
2106
2627
|
"smart_bid",
|
|
@@ -2123,6 +2644,36 @@ var SessionsResource = class {
|
|
|
2123
2644
|
this.client = client;
|
|
2124
2645
|
}
|
|
2125
2646
|
client;
|
|
2647
|
+
getMcpApp(sessionId, sourceId, init) {
|
|
2648
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/mcp-apps/${encodeURIComponent(sourceId)}`, init);
|
|
2649
|
+
}
|
|
2650
|
+
onMcpAppChanged(sessionId, sourceId, listener) {
|
|
2651
|
+
let socket = this.client.socket();
|
|
2652
|
+
const changed = (payload) => {
|
|
2653
|
+
if (payload.session_id === sessionId && payload.source_id === sourceId) listener();
|
|
2654
|
+
};
|
|
2655
|
+
socket.on("mcp-app:changed", changed);
|
|
2656
|
+
socket.on("connect", listener);
|
|
2657
|
+
const off = this.client.onSocketReplaced(() => {
|
|
2658
|
+
socket.off("mcp-app:changed", changed);
|
|
2659
|
+
socket.off("connect", listener);
|
|
2660
|
+
socket = this.client.socket();
|
|
2661
|
+
socket.on("mcp-app:changed", changed);
|
|
2662
|
+
socket.on("connect", listener);
|
|
2663
|
+
});
|
|
2664
|
+
return () => {
|
|
2665
|
+
off();
|
|
2666
|
+
socket.off("mcp-app:changed", changed);
|
|
2667
|
+
socket.off("connect", listener);
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
callMcpApp(sessionId, sourceId, request, init) {
|
|
2671
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/mcp-apps/${encodeURIComponent(sourceId)}/calls`, {
|
|
2672
|
+
...init,
|
|
2673
|
+
method: "POST",
|
|
2674
|
+
body: JSON.stringify(request)
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2126
2677
|
/**
|
|
2127
2678
|
* 连接一个已存在的会话,返回实时会话对象 AgentSession
|
|
2128
2679
|
* (自动完成历史加载、Socket.IO 订阅、断线重连补数)。
|
|
@@ -2130,6 +2681,56 @@ var SessionsResource = class {
|
|
|
2130
2681
|
connect(sessionId) {
|
|
2131
2682
|
return this.client.hub.connect(sessionId);
|
|
2132
2683
|
}
|
|
2684
|
+
listSessionPlugins(sessionId, init) {
|
|
2685
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins`, init);
|
|
2686
|
+
}
|
|
2687
|
+
setSessionPluginActivation(sessionId, name, active, init) {
|
|
2688
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}:activation`, { method: "POST", body: JSON.stringify({ active }), ...init });
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* Fetch a plugin's package-local icon as bytes.
|
|
2692
|
+
*
|
|
2693
|
+
* The request carries the client's own bearer credentials, so callers turn
|
|
2694
|
+
* the blob into an object URL instead of pointing `<img>` at an API path and
|
|
2695
|
+
* hoping the request is authenticated.
|
|
2696
|
+
*/
|
|
2697
|
+
fetchSessionPluginIcon(sessionId, name) {
|
|
2698
|
+
return this.client.blob("GET", `/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/icon`);
|
|
2699
|
+
}
|
|
2700
|
+
/** Authenticated URL for the icon; useful where a blob URL cannot be used. */
|
|
2701
|
+
sessionPluginIconUrl(sessionId, name) {
|
|
2702
|
+
return this.client.buildAuthedUrl(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/icon`);
|
|
2703
|
+
}
|
|
2704
|
+
/**
|
|
2705
|
+
* 准备一个插件的配置:把包准备好并读出用户的配置契约,**不启用**它。
|
|
2706
|
+
*
|
|
2707
|
+
* 开启插件前必须先走这一步:缺配置时用户只会看到配置表单,服务端不会先把
|
|
2708
|
+
* active 持久化成 true 再靠补偿关掉。它不修改会话的插件选择、不连接 MCP。
|
|
2709
|
+
*/
|
|
2710
|
+
prepareSessionPluginConfig(sessionId, name, init) {
|
|
2711
|
+
return this.client.jsonFromInit(
|
|
2712
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}:prepare-config`,
|
|
2713
|
+
{ method: "POST", ...init }
|
|
2714
|
+
);
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Read a plugin's configuration contract and the user's stored values.
|
|
2718
|
+
*
|
|
2719
|
+
* Secrets are never returned: `write_only` lists the pointers that hold one.
|
|
2720
|
+
*/
|
|
2721
|
+
getSessionPluginConfig(sessionId, name, init) {
|
|
2722
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, init);
|
|
2723
|
+
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Publish explicit set/remove operations to the user's keychain file.
|
|
2726
|
+
*
|
|
2727
|
+
* Fields absent from both lists keep their stored value, so a form never has
|
|
2728
|
+
* to round-trip a secret. Rejections leave the previous file untouched; the
|
|
2729
|
+
* call never restarts MCP servers.
|
|
2730
|
+
*/
|
|
2731
|
+
updateSessionPluginConfig(sessionId, name, update, init) {
|
|
2732
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, { method: "PUT", body: JSON.stringify(update), ...init });
|
|
2733
|
+
}
|
|
2133
2734
|
/** 创建新会话并直接连接,返回可收发消息的 AgentSession。 */
|
|
2134
2735
|
async create(request = {}) {
|
|
2135
2736
|
if (isSessionDefinition(request)) {
|
|
@@ -2226,7 +2827,8 @@ var SessionsResource = class {
|
|
|
2226
2827
|
template_id_prefix,
|
|
2227
2828
|
solution_id,
|
|
2228
2829
|
solution_registry_id,
|
|
2229
|
-
q
|
|
2830
|
+
q,
|
|
2831
|
+
chat_group_id
|
|
2230
2832
|
}) {
|
|
2231
2833
|
const searchParams = new URLSearchParams();
|
|
2232
2834
|
searchParams.set("limit", String(limit));
|
|
@@ -2239,6 +2841,7 @@ var SessionsResource = class {
|
|
|
2239
2841
|
}
|
|
2240
2842
|
const trimmedQuery = q?.trim();
|
|
2241
2843
|
if (trimmedQuery) searchParams.set("q", trimmedQuery);
|
|
2844
|
+
if (chat_group_id) searchParams.set("chat_group_id", chat_group_id);
|
|
2242
2845
|
return this.client.json("GET", `/api/sessions?${searchParams.toString()}`);
|
|
2243
2846
|
}
|
|
2244
2847
|
async listSessions(template_id_prefix, solution_id) {
|
|
@@ -2329,6 +2932,37 @@ var SessionsResource = class {
|
|
|
2329
2932
|
);
|
|
2330
2933
|
return projectHistory(entries);
|
|
2331
2934
|
}
|
|
2935
|
+
async getSessionTurnsPage(sessionId, options = {}) {
|
|
2936
|
+
const query = new URLSearchParams();
|
|
2937
|
+
if (options.limit !== void 0) query.set("limit", String(options.limit));
|
|
2938
|
+
if (options.before !== void 0) query.set("before", options.before);
|
|
2939
|
+
const suffix = query.size ? `?${query}` : "";
|
|
2940
|
+
const page = await this.client.json("GET", `/api/sessions/${encodeURIComponent(sessionId)}/turns${suffix}`);
|
|
2941
|
+
return {
|
|
2942
|
+
turns: page.turns,
|
|
2943
|
+
nextBefore: page.next_before,
|
|
2944
|
+
currentMode: page.current_mode
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
getMcpAppContext(sessionId, sourceId, init) {
|
|
2948
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/mcp-apps/${encodeURIComponent(sourceId)}/context`, init);
|
|
2949
|
+
}
|
|
2950
|
+
updateMcpAppContext(sessionId, sourceId, body, init) {
|
|
2951
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/mcp-apps/${encodeURIComponent(sourceId)}/context`, {
|
|
2952
|
+
...init,
|
|
2953
|
+
method: "POST",
|
|
2954
|
+
body: JSON.stringify(body)
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
async getSessionTurnCommands(sessionId) {
|
|
2958
|
+
const commands = await this.client.json("GET", `/api/sessions/${encodeURIComponent(sessionId)}/turn-commands`);
|
|
2959
|
+
return commands.map((command) => ({
|
|
2960
|
+
deliveryId: command.delivery_id,
|
|
2961
|
+
toolCallId: command.tool_call_id ?? void 0,
|
|
2962
|
+
action: command.action,
|
|
2963
|
+
payload: command.payload
|
|
2964
|
+
}));
|
|
2965
|
+
}
|
|
2332
2966
|
listResultFeedback(sessionId) {
|
|
2333
2967
|
return this.client.json("GET", `/api/sessions/${encodeURIComponent(sessionId)}/feedback`);
|
|
2334
2968
|
}
|
|
@@ -2592,6 +3226,129 @@ function profileToCreateSessionRequest(profile) {
|
|
|
2592
3226
|
};
|
|
2593
3227
|
}
|
|
2594
3228
|
|
|
3229
|
+
// src/resources/plugins.ts
|
|
3230
|
+
var PluginsResource = class {
|
|
3231
|
+
constructor(client) {
|
|
3232
|
+
this.client = client;
|
|
3233
|
+
}
|
|
3234
|
+
client;
|
|
3235
|
+
/**
|
|
3236
|
+
* 列出当前用户安装的插件及其展示信息。
|
|
3237
|
+
*
|
|
3238
|
+
* 服务端可能为了补齐名称/图标惰性获取包内容,但绝不激活插件、不连 MCP;
|
|
3239
|
+
* 单个插件的展示信息读不出来只会降级成占位,不会让整份目录失败。
|
|
3240
|
+
*/
|
|
3241
|
+
catalog(init) {
|
|
3242
|
+
return this.client.jsonFromInit("/api/plugins/catalog", init);
|
|
3243
|
+
}
|
|
3244
|
+
/** 取一个插件的包内图标字节(用户级,不需要会话)。 */
|
|
3245
|
+
fetchIcon(name) {
|
|
3246
|
+
return this.client.blob("GET", `/api/plugins/${encodeURIComponent(name)}/icon`);
|
|
3247
|
+
}
|
|
3248
|
+
/** 带凭据的图标地址;能直接用 blob URL 的地方优先用 `fetchIcon`。 */
|
|
3249
|
+
iconUrl(name) {
|
|
3250
|
+
return this.client.buildAuthedUrl(`/api/plugins/${encodeURIComponent(name)}/icon`);
|
|
3251
|
+
}
|
|
3252
|
+
};
|
|
3253
|
+
|
|
3254
|
+
// src/resources/chat-groups.ts
|
|
3255
|
+
var ChatGroupsResource = class {
|
|
3256
|
+
constructor(client) {
|
|
3257
|
+
this.client = client;
|
|
3258
|
+
}
|
|
3259
|
+
client;
|
|
3260
|
+
list() {
|
|
3261
|
+
return this.client.json("GET", "/api/chat-groups");
|
|
3262
|
+
}
|
|
3263
|
+
create(payload) {
|
|
3264
|
+
return this.client.json("POST", "/api/chat-groups", payload);
|
|
3265
|
+
}
|
|
3266
|
+
update(groupId, payload) {
|
|
3267
|
+
return this.client.json("PATCH", `/api/chat-groups/${encodeURIComponent(groupId)}`, payload);
|
|
3268
|
+
}
|
|
3269
|
+
delete(groupId) {
|
|
3270
|
+
return this.client.json("DELETE", `/api/chat-groups/${encodeURIComponent(groupId)}`);
|
|
3271
|
+
}
|
|
3272
|
+
};
|
|
3273
|
+
|
|
3274
|
+
// src/resources/site-visits.ts
|
|
3275
|
+
var SITE_VISITS_PATH = "/api/site-visits";
|
|
3276
|
+
var REQUEST_TIMEOUT_MS = 5e3;
|
|
3277
|
+
var SiteVisitsResource = class {
|
|
3278
|
+
constructor(client) {
|
|
3279
|
+
this.client = client;
|
|
3280
|
+
}
|
|
3281
|
+
client;
|
|
3282
|
+
/** 上报一次页面访问,返回当前快照;不承诺本次已计入或已落盘。 */
|
|
3283
|
+
record() {
|
|
3284
|
+
return this.request("POST");
|
|
3285
|
+
}
|
|
3286
|
+
/** 只读快照:不设置访客 Cookie,也不增加任何计数。 */
|
|
3287
|
+
summary() {
|
|
3288
|
+
return this.request("GET");
|
|
3289
|
+
}
|
|
3290
|
+
async request(method) {
|
|
3291
|
+
const url = buildBackendUrl(this.client.options.baseUrl, SITE_VISITS_PATH);
|
|
3292
|
+
const controller = new AbortController();
|
|
3293
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
3294
|
+
try {
|
|
3295
|
+
const response = await (this.client.options.fetchImpl ?? fetch)(url.toString(), {
|
|
3296
|
+
method,
|
|
3297
|
+
// 访客 Cookie 是唯一的去重依据,必须随请求往返。
|
|
3298
|
+
credentials: "include",
|
|
3299
|
+
signal: controller.signal
|
|
3300
|
+
});
|
|
3301
|
+
if (!response.ok) {
|
|
3302
|
+
throw new BladeApiError(response, await extractErrorDetail(response));
|
|
3303
|
+
}
|
|
3304
|
+
return await response.json();
|
|
3305
|
+
} finally {
|
|
3306
|
+
clearTimeout(timeout);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
};
|
|
3310
|
+
|
|
3311
|
+
// src/shared/auth-busy.ts
|
|
3312
|
+
var DEFAULT_RETRY_AFTER_SECONDS = 1;
|
|
3313
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
3314
|
+
function errorData(error) {
|
|
3315
|
+
if (typeof error !== "object" || error === null || !("data" in error)) return null;
|
|
3316
|
+
const data = error.data;
|
|
3317
|
+
return typeof data === "object" && data !== null ? data : null;
|
|
3318
|
+
}
|
|
3319
|
+
function authBusyRetryDelayMs(error) {
|
|
3320
|
+
const data = errorData(error);
|
|
3321
|
+
if (data?.code !== "auth_busy" || Number(data.status) !== 429) return null;
|
|
3322
|
+
const rawRetryAfter = data.retry_after;
|
|
3323
|
+
const seconds = rawRetryAfter == null || String(rawRetryAfter).trim() === "" ? Number.NaN : Number(rawRetryAfter);
|
|
3324
|
+
const delaySeconds = Number.isFinite(seconds) && seconds >= 0 ? seconds : DEFAULT_RETRY_AFTER_SECONDS;
|
|
3325
|
+
return Math.min(delaySeconds * 1e3, MAX_TIMER_DELAY_MS);
|
|
3326
|
+
}
|
|
3327
|
+
var AuthBusyReconnect = class {
|
|
3328
|
+
timer = null;
|
|
3329
|
+
get pending() {
|
|
3330
|
+
return this.timer !== null;
|
|
3331
|
+
}
|
|
3332
|
+
schedule(error, reconnect) {
|
|
3333
|
+
const delay = authBusyRetryDelayMs(error);
|
|
3334
|
+
if (delay === null) {
|
|
3335
|
+
this.cancel();
|
|
3336
|
+
return false;
|
|
3337
|
+
}
|
|
3338
|
+
this.cancel();
|
|
3339
|
+
this.timer = globalThis.setTimeout(() => {
|
|
3340
|
+
this.timer = null;
|
|
3341
|
+
reconnect();
|
|
3342
|
+
}, delay);
|
|
3343
|
+
return true;
|
|
3344
|
+
}
|
|
3345
|
+
cancel() {
|
|
3346
|
+
if (this.timer === null) return;
|
|
3347
|
+
globalThis.clearTimeout(this.timer);
|
|
3348
|
+
this.timer = null;
|
|
3349
|
+
}
|
|
3350
|
+
};
|
|
3351
|
+
|
|
2595
3352
|
// src/commands/registry.ts
|
|
2596
3353
|
var BUFFER_LIMIT_PER_ACTION = 20;
|
|
2597
3354
|
var CommandRegistry = class {
|
|
@@ -2700,22 +3457,290 @@ var Emitter = class {
|
|
|
2700
3457
|
}
|
|
2701
3458
|
};
|
|
2702
3459
|
|
|
2703
|
-
// src/
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
];
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
}
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
3460
|
+
// src/session/history-page.ts
|
|
3461
|
+
function sortedUnique(turns) {
|
|
3462
|
+
const byId = /* @__PURE__ */ new Map();
|
|
3463
|
+
for (const turn of turns) byId.set(turn.turn_id, turn);
|
|
3464
|
+
return [...byId.values()].sort((left, right) => left.sequence - right.sequence);
|
|
3465
|
+
}
|
|
3466
|
+
function prependOlder(current, page) {
|
|
3467
|
+
const currentById = new Map(current.map((turn) => [turn.turn_id, turn]));
|
|
3468
|
+
return sortedUnique([
|
|
3469
|
+
...current,
|
|
3470
|
+
...page.map((turn) => {
|
|
3471
|
+
const live = currentById.get(turn.turn_id);
|
|
3472
|
+
return live ? { ...live, sequence: turn.sequence } : turn;
|
|
3473
|
+
})
|
|
3474
|
+
]);
|
|
3475
|
+
}
|
|
3476
|
+
function replaceWindow(current, page, liveRevisionAtStart, liveRevisions) {
|
|
3477
|
+
const pageById = new Map(page.map((turn) => [turn.turn_id, turn]));
|
|
3478
|
+
const newerLive = current.filter(
|
|
3479
|
+
(turn) => turn.turn_id.startsWith("local-user-") || (liveRevisions.byTurnId.get(turn.turn_id) ?? 0) > liveRevisionAtStart
|
|
3480
|
+
);
|
|
3481
|
+
return sortedUnique([
|
|
3482
|
+
...page,
|
|
3483
|
+
...newerLive.map((turn) => {
|
|
3484
|
+
const snapshot2 = pageById.get(turn.turn_id);
|
|
3485
|
+
return snapshot2 ? { ...turn, sequence: snapshot2.sequence } : turn;
|
|
3486
|
+
})
|
|
3487
|
+
]);
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// src/session/queue.ts
|
|
3491
|
+
var EMPTY_SESSION_QUEUE = {
|
|
3492
|
+
session_id: "",
|
|
3493
|
+
revision: 0,
|
|
3494
|
+
paused: false,
|
|
3495
|
+
pause_reason: null,
|
|
3496
|
+
items: []
|
|
3497
|
+
};
|
|
3498
|
+
var QUEUE_FAILURE_MESSAGE = "\u961F\u5217\u64CD\u4F5C\u5931\u8D25";
|
|
3499
|
+
var QUEUED_MESSAGE_STATUSES = [
|
|
3500
|
+
"pending",
|
|
3501
|
+
"delivering",
|
|
3502
|
+
"claimed",
|
|
3503
|
+
"consumed",
|
|
3504
|
+
"cancelled"
|
|
3505
|
+
];
|
|
3506
|
+
function toQueuedMessage(value) {
|
|
3507
|
+
if (!isRecord(value)) return null;
|
|
3508
|
+
const status = typeof value.status === "string" && QUEUED_MESSAGE_STATUSES.includes(value.status) ? value.status : null;
|
|
3509
|
+
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) {
|
|
3510
|
+
return null;
|
|
3511
|
+
}
|
|
3512
|
+
const item = {
|
|
3513
|
+
id: value.id,
|
|
3514
|
+
content: value.content,
|
|
3515
|
+
position: value.position,
|
|
3516
|
+
version: value.version,
|
|
3517
|
+
status,
|
|
3518
|
+
created_at: value.created_at,
|
|
3519
|
+
updated_at: value.updated_at
|
|
3520
|
+
};
|
|
3521
|
+
if (typeof value.target_run_id === "string") item.target_run_id = value.target_run_id;
|
|
3522
|
+
if (typeof value.error === "string") item.error = value.error;
|
|
3523
|
+
return item;
|
|
3524
|
+
}
|
|
3525
|
+
function toQueueSnapshot(value, fallbackSessionId) {
|
|
3526
|
+
if (!isRecord(value)) return null;
|
|
3527
|
+
if (typeof value.revision !== "number" || !Number.isInteger(value.revision) || value.revision < 0) {
|
|
3528
|
+
return null;
|
|
3529
|
+
}
|
|
3530
|
+
if (typeof value.paused !== "boolean") return null;
|
|
3531
|
+
if (!Array.isArray(value.items)) return null;
|
|
3532
|
+
const pauseReason = value.pause_reason;
|
|
3533
|
+
if (pauseReason !== null && pauseReason !== void 0 && typeof pauseReason !== "string") {
|
|
3534
|
+
return null;
|
|
3535
|
+
}
|
|
3536
|
+
const items = [];
|
|
3537
|
+
for (const raw of value.items) {
|
|
3538
|
+
const item = toQueuedMessage(raw);
|
|
3539
|
+
if (!item) return null;
|
|
3540
|
+
items.push(item);
|
|
3541
|
+
}
|
|
3542
|
+
return {
|
|
3543
|
+
session_id: typeof value.session_id === "string" && value.session_id ? value.session_id : fallbackSessionId,
|
|
3544
|
+
revision: value.revision,
|
|
3545
|
+
paused: value.paused,
|
|
3546
|
+
pause_reason: typeof pauseReason === "string" ? pauseReason : null,
|
|
3547
|
+
items
|
|
3548
|
+
};
|
|
3549
|
+
}
|
|
3550
|
+
function isSessionQueueSnapshot(value) {
|
|
3551
|
+
if (!isRecord(value) || typeof value.session_id !== "string") return false;
|
|
3552
|
+
return toQueueSnapshot(value, value.session_id) !== null;
|
|
3553
|
+
}
|
|
3554
|
+
function shouldApplySnapshot(current, next) {
|
|
3555
|
+
if (!isSessionQueueSnapshot(next)) return false;
|
|
3556
|
+
if (current?.session_id && next.session_id !== current.session_id) return false;
|
|
3557
|
+
return next.revision > (current?.revision ?? -1);
|
|
3558
|
+
}
|
|
3559
|
+
function parseQueueAck(response, fallbackSessionId) {
|
|
3560
|
+
if (!isRecord(response)) {
|
|
3561
|
+
return { ok: false, conflict: false, snapshot: null, message: QUEUE_FAILURE_MESSAGE };
|
|
3562
|
+
}
|
|
3563
|
+
const message = typeof response.message === "string" && response.message ? response.message : null;
|
|
3564
|
+
const status = response.status;
|
|
3565
|
+
if (status !== "ok" && status !== "conflict" && status !== "queued") {
|
|
3566
|
+
return { ok: false, conflict: false, snapshot: null, message: message ?? QUEUE_FAILURE_MESSAGE };
|
|
3567
|
+
}
|
|
3568
|
+
const snapshot2 = toQueueSnapshot(response, fallbackSessionId);
|
|
3569
|
+
if (status === "conflict") {
|
|
3570
|
+
return { ok: false, conflict: true, snapshot: snapshot2, message: message ?? QUEUE_FAILURE_MESSAGE };
|
|
3571
|
+
}
|
|
3572
|
+
if (!snapshot2) {
|
|
3573
|
+
return { ok: false, conflict: false, snapshot: null, message: message ?? QUEUE_FAILURE_MESSAGE };
|
|
3574
|
+
}
|
|
3575
|
+
return { ok: true, conflict: false, snapshot: snapshot2, message };
|
|
3576
|
+
}
|
|
3577
|
+
function canEditQueuedMessage(item) {
|
|
3578
|
+
return item.status === "pending";
|
|
3579
|
+
}
|
|
3580
|
+
function canCancelQueuedMessage(item) {
|
|
3581
|
+
return item.status === "pending";
|
|
3582
|
+
}
|
|
3583
|
+
function canDeliverQueuedMessage(item) {
|
|
3584
|
+
return item.status === "pending";
|
|
3585
|
+
}
|
|
3586
|
+
var QUEUED_MESSAGE_STATUS_LABELS = {
|
|
3587
|
+
pending: "\u5F85\u6267\u884C",
|
|
3588
|
+
delivering: "\u7B49\u5F85\u63A5\u6536",
|
|
3589
|
+
claimed: "",
|
|
3590
|
+
consumed: "",
|
|
3591
|
+
cancelled: ""
|
|
3592
|
+
};
|
|
3593
|
+
function queuedMessageStatusLabel(status) {
|
|
3594
|
+
return QUEUED_MESSAGE_STATUS_LABELS[status] ?? "";
|
|
3595
|
+
}
|
|
3596
|
+
var QUEUE_PAUSE_REASON_LABELS = {
|
|
3597
|
+
user_stop: "\u5DF2\u6682\u505C",
|
|
3598
|
+
run_failed: "\u4EFB\u52A1\u5F02\u5E38\u5DF2\u6682\u505C",
|
|
3599
|
+
run_unknown: "\u4EFB\u52A1\u7ED3\u675F\u72B6\u6001\u672A\u77E5\uFF0C\u5DF2\u6682\u505C",
|
|
3600
|
+
server_restart: "\u670D\u52A1\u91CD\u542F\u5DF2\u6682\u505C"
|
|
3601
|
+
};
|
|
3602
|
+
function queuePauseReasonLabel(reason) {
|
|
3603
|
+
if (!reason) return null;
|
|
3604
|
+
return QUEUE_PAUSE_REASON_LABELS[reason] ?? reason;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
// src/schemas/mcp-app.ts
|
|
3608
|
+
function isMcpAppData(value) {
|
|
3609
|
+
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);
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
// src/shared/tool-ui-card.ts
|
|
3613
|
+
function isNonEmptyString(value) {
|
|
3614
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
3615
|
+
}
|
|
3616
|
+
function isPositiveNumber(value) {
|
|
3617
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
3618
|
+
}
|
|
3619
|
+
function isToolUiCard(value) {
|
|
3620
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3621
|
+
return false;
|
|
3622
|
+
}
|
|
3623
|
+
const raw = value;
|
|
3624
|
+
if (raw.target !== "inline" && raw.target !== "preview") {
|
|
3625
|
+
return false;
|
|
3626
|
+
}
|
|
3627
|
+
if (!isPositiveNumber(raw.height)) {
|
|
3628
|
+
return false;
|
|
3629
|
+
}
|
|
3630
|
+
if (raw.archived === true && !isNonEmptyString(raw.resourceHTML)) {
|
|
3631
|
+
return false;
|
|
3632
|
+
}
|
|
3633
|
+
if (!isNonEmptyString(raw.resourceHTML) && !isNonEmptyString(raw.resourceUri) && !isNonEmptyString(raw.resourceURI)) {
|
|
3634
|
+
return false;
|
|
3635
|
+
}
|
|
3636
|
+
if (raw.title != null && !isNonEmptyString(raw.title)) {
|
|
3637
|
+
return false;
|
|
3638
|
+
}
|
|
3639
|
+
if (raw.mcpApp !== void 0 && !isMcpAppData(raw.mcpApp)) {
|
|
3640
|
+
return false;
|
|
3641
|
+
}
|
|
3642
|
+
return true;
|
|
3643
|
+
}
|
|
3644
|
+
function resolveToolUiCardContent(card) {
|
|
3645
|
+
if (isNonEmptyString(card.resourceHTML)) {
|
|
3646
|
+
return { type: "resource-html", content: card.resourceHTML };
|
|
3647
|
+
}
|
|
3648
|
+
if (card.archived) return null;
|
|
3649
|
+
const uri = isNonEmptyString(card.resourceUri) ? card.resourceUri : card.resourceURI;
|
|
3650
|
+
return isNonEmptyString(uri) ? { type: "resource-uri", content: uri } : null;
|
|
3651
|
+
}
|
|
3652
|
+
function buildToolUiCardKey(toolCallId, type3, content) {
|
|
3653
|
+
if (type3 === "resource-uri") {
|
|
3654
|
+
return `tool-preview-uri:${content}`;
|
|
3655
|
+
}
|
|
3656
|
+
return `tool-preview:${toolCallId}`;
|
|
3657
|
+
}
|
|
3658
|
+
function isInternalStatusToolUiCard(card) {
|
|
3659
|
+
return card?.title?.trim() === "\u9636\u6BB5\u8FDB\u5EA6";
|
|
3660
|
+
}
|
|
3661
|
+
function isAppDevToolUiCard(card, isAppDev) {
|
|
3662
|
+
return isAppDev && card.target === "preview" && !card.resourceHTML && Boolean(card.resourceUri || card.resourceURI);
|
|
3663
|
+
}
|
|
3664
|
+
function collectInlineToolUiCards(messages) {
|
|
3665
|
+
return messages.flatMap(
|
|
3666
|
+
(message) => (message.blocks ?? []).flatMap(
|
|
3667
|
+
(block, index) => block.type === "tool_ui" && block.tool_call_id && isToolUiCard(block.content) && block.content.target === "inline" ? [
|
|
3668
|
+
{
|
|
3669
|
+
key: `${message.entry_id ?? message.timestamp ?? "message"}-${block.tool_call_id}-${index}`,
|
|
3670
|
+
toolCallId: block.tool_call_id,
|
|
3671
|
+
card: block.content
|
|
3672
|
+
}
|
|
3673
|
+
] : []
|
|
3674
|
+
)
|
|
3675
|
+
);
|
|
3676
|
+
}
|
|
3677
|
+
function collectPreviewToolUiCards(messages) {
|
|
3678
|
+
return messages.flatMap(
|
|
3679
|
+
(message) => (message.blocks ?? []).flatMap((block, index) => {
|
|
3680
|
+
if (block.type !== "tool_ui" || !block.tool_call_id || !isToolUiCard(block.content) || block.content.target !== "preview") {
|
|
3681
|
+
return [];
|
|
3682
|
+
}
|
|
3683
|
+
const toolCall = (message.tool_calls ?? []).find(
|
|
3684
|
+
(candidate) => candidate.id === block.tool_call_id
|
|
3685
|
+
);
|
|
3686
|
+
return toolCall ? [
|
|
3687
|
+
{
|
|
3688
|
+
key: `${message.entry_id ?? message.timestamp ?? "message"}-${block.tool_call_id}-${index}`,
|
|
3689
|
+
toolCall,
|
|
3690
|
+
card: block.content,
|
|
3691
|
+
blocks: message.blocks ?? []
|
|
3692
|
+
}
|
|
3693
|
+
] : [];
|
|
3694
|
+
})
|
|
3695
|
+
);
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
// src/schemas/message-utils.ts
|
|
3699
|
+
var SYSTEM_REMINDER_PATTERN = /^<system-reminder>\s*[\s\S]*?\s*<\/system-reminder>$/i;
|
|
3700
|
+
var SYSTEM_NOTIFICATION_PATTERN = /^<system-notification>\s*[\s\S]*?\s*<\/system-notification>$/i;
|
|
3701
|
+
var LEGACY_FORK_CONTEXT_MARKERS = [
|
|
3702
|
+
"\u7236\u667A\u80FD\u4F53\u5728\u6D3E\u751F\u4F60\u4E4B\u524D\u5DF2\u7ECF\u5B8C\u6210\u4E86\u4E00\u4E9B\u64CD\u4F5C\u3002",
|
|
3703
|
+
"\u53EF\u901A\u8FC7 `read_file` \u8BFB\u53D6\u3002",
|
|
3704
|
+
"# \u4F60\u7684\u4EFB\u52A1"
|
|
3705
|
+
];
|
|
3706
|
+
var INTERNAL_CHAT_ERROR_PATTERNS = [
|
|
3707
|
+
/\b(?:ENOENT|EACCES|EPERM)\b/i,
|
|
3708
|
+
/\[(?:Errno -?\d+|WinError \d+)\]/i,
|
|
3709
|
+
/\b(?:ECONNRESET|ECONNREFUSED|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|ENOTFOUND|EAI_AGAIN)\b/i,
|
|
3710
|
+
/\b(?:Connection refused|Connection reset by peer|All connection attempts failed|getaddrinfo failed)\b/i,
|
|
3711
|
+
/(?:^|[\s("'`])\/(?:root|home|Users|tmp|var|opt|etc)\//,
|
|
3712
|
+
/[A-Za-z]:\\(?:Users|Windows|Program Files|AppData)\\/i,
|
|
3713
|
+
/(?:^|\n)Traceback \(most recent call last\):/,
|
|
3714
|
+
/(?:^|\n)\s*at\s+\S+(?:\s+\(|:\d+:\d+)/,
|
|
3715
|
+
/(?:^|\n)File "[^"]+", line \d+/
|
|
3716
|
+
];
|
|
3717
|
+
var SAFE_CHAT_ERROR_PREFIXES = [
|
|
3718
|
+
"\u6C99\u76D2\u8FD0\u884C\u73AF\u5883\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
|
|
3719
|
+
"\u6A21\u578B\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u68C0\u67E5\u6A21\u578B\u914D\u7F6E\u540E\u91CD\u8BD5\u3002"
|
|
3720
|
+
];
|
|
3721
|
+
var GENERIC_CHAT_ERROR = "\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002";
|
|
3722
|
+
function chatErrorForDisplay(message, developer = false) {
|
|
3723
|
+
const normalized = message.trim();
|
|
3724
|
+
if (!developer) {
|
|
3725
|
+
const safePrefix = SAFE_CHAT_ERROR_PREFIXES.find((prefix) => normalized.startsWith(prefix));
|
|
3726
|
+
if (safePrefix) return safePrefix;
|
|
3727
|
+
}
|
|
3728
|
+
if (!developer && /no source matches this model/i.test(normalized)) {
|
|
3729
|
+
return "\u5F53\u524D\u6A21\u578B\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u6216\u91CD\u65B0\u9009\u62E9\u6A21\u578B\u3002";
|
|
3730
|
+
}
|
|
3731
|
+
if (!developer && INTERNAL_CHAT_ERROR_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
3732
|
+
return GENERIC_CHAT_ERROR;
|
|
3733
|
+
}
|
|
3734
|
+
return normalized || GENERIC_CHAT_ERROR;
|
|
3735
|
+
}
|
|
3736
|
+
function normalizeMessageContent(content) {
|
|
3737
|
+
if (typeof content === "string") return content;
|
|
3738
|
+
if (Array.isArray(content)) return content;
|
|
3739
|
+
return String(content ?? "");
|
|
3740
|
+
}
|
|
3741
|
+
function getTextContent(content) {
|
|
3742
|
+
if (typeof content === "string") return content;
|
|
3743
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
2719
3744
|
}
|
|
2720
3745
|
function isSystemReminderText(text) {
|
|
2721
3746
|
return SYSTEM_REMINDER_PATTERN.test(text.trim());
|
|
@@ -2727,10 +3752,10 @@ function isLegacyForkContextText(text) {
|
|
|
2727
3752
|
const normalized = text.trim();
|
|
2728
3753
|
return LEGACY_FORK_CONTEXT_MARKERS.every((marker) => normalized.includes(marker));
|
|
2729
3754
|
}
|
|
2730
|
-
function isHiddenInternalMessage(message) {
|
|
3755
|
+
function isHiddenInternalMessage(message, options = {}) {
|
|
2731
3756
|
if (message.role !== "user") return false;
|
|
2732
3757
|
const text = getTextContent(normalizeMessageContent(message.content));
|
|
2733
|
-
return isSystemReminderText(text) || isSystemNotificationText(text) || isLegacyForkContextText(text);
|
|
3758
|
+
return isSystemReminderText(text) || isSystemNotificationText(text) || (options.includeLegacyForkContext ?? true) && isLegacyForkContextText(text);
|
|
2734
3759
|
}
|
|
2735
3760
|
function buildMessageContent(text, attachments) {
|
|
2736
3761
|
if (attachments.length === 0) return text;
|
|
@@ -2877,7 +3902,10 @@ function createInitialSessionState(sessionId) {
|
|
|
2877
3902
|
agentLoops: {},
|
|
2878
3903
|
activeCompaction: null,
|
|
2879
3904
|
replay: null,
|
|
2880
|
-
|
|
3905
|
+
queue: EMPTY_SESSION_QUEUE,
|
|
3906
|
+
viewerRole: null,
|
|
3907
|
+
nextBefore: null,
|
|
3908
|
+
loadingOlder: false
|
|
2881
3909
|
};
|
|
2882
3910
|
}
|
|
2883
3911
|
function parseAgentDescription(argumentsJson) {
|
|
@@ -2903,10 +3931,10 @@ function inferLoopStatusFromMessages(messages) {
|
|
|
2903
3931
|
}
|
|
2904
3932
|
function inferLoopStatusFromTurns(turns, messages) {
|
|
2905
3933
|
const latestAgentNotification = [...turns].reverse().flatMap((turn) => turn.blocks).find((block) => {
|
|
2906
|
-
if (block.type !== "system_notification" || !
|
|
3934
|
+
if (block.type !== "system_notification" || !isRecord(block.content)) return false;
|
|
2907
3935
|
return block.content.notification_type === "agent:start" || block.content.notification_type === "agent:end";
|
|
2908
3936
|
});
|
|
2909
|
-
if (latestAgentNotification?.type === "system_notification" &&
|
|
3937
|
+
if (latestAgentNotification?.type === "system_notification" && isRecord(latestAgentNotification.content)) {
|
|
2910
3938
|
const notificationType = latestAgentNotification.content.notification_type;
|
|
2911
3939
|
const status = latestAgentNotification.content.status;
|
|
2912
3940
|
if (notificationType === "agent:start" || status === "running") return "running";
|
|
@@ -2915,17 +3943,44 @@ function inferLoopStatusFromTurns(turns, messages) {
|
|
|
2915
3943
|
}
|
|
2916
3944
|
return inferLoopStatusFromMessages(messages);
|
|
2917
3945
|
}
|
|
2918
|
-
function
|
|
2919
|
-
|
|
3946
|
+
function toSelectionMap(value) {
|
|
3947
|
+
if (!isRecord(value)) return {};
|
|
3948
|
+
const entries = Object.entries(value).map(([questionKey, optionIndexes]) => {
|
|
3949
|
+
if (!Array.isArray(optionIndexes)) return null;
|
|
3950
|
+
const parsedIndexes = optionIndexes.map((item) => typeof item === "number" ? item : Number(item)).filter((item) => Number.isInteger(item));
|
|
3951
|
+
return [Number(questionKey), parsedIndexes];
|
|
3952
|
+
}).filter((entry) => entry !== null);
|
|
3953
|
+
return Object.fromEntries(entries);
|
|
3954
|
+
}
|
|
3955
|
+
function toCustomMap(value) {
|
|
3956
|
+
if (!isRecord(value)) return {};
|
|
3957
|
+
const entries = Object.entries(value).filter(([, text]) => typeof text === "string").map(([questionKey, text]) => [Number(questionKey), text]);
|
|
3958
|
+
return Object.fromEntries(entries);
|
|
3959
|
+
}
|
|
3960
|
+
function extractAskAnswers(turns) {
|
|
3961
|
+
const answers = {};
|
|
3962
|
+
for (const turn of turns) {
|
|
3963
|
+
for (const block of turn.blocks) {
|
|
3964
|
+
if (block.type !== "ask_user_answer" || typeof block.tool_call_id !== "string") continue;
|
|
3965
|
+
if (!isRecord(block.content)) continue;
|
|
3966
|
+
const note = typeof block.content.note === "string" ? block.content.note.trim() : "";
|
|
3967
|
+
answers[block.tool_call_id] = {
|
|
3968
|
+
selections: toSelectionMap(block.content.selections),
|
|
3969
|
+
custom: toCustomMap(block.content.custom),
|
|
3970
|
+
...note ? { note } : {}
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
return answers;
|
|
2920
3975
|
}
|
|
2921
3976
|
function parentForkToolCallIdFromTurn(turn) {
|
|
2922
3977
|
if (typeof turn.parent_fork_tool_call_id === "string" && turn.parent_fork_tool_call_id.length > 0) {
|
|
2923
3978
|
return turn.parent_fork_tool_call_id;
|
|
2924
3979
|
}
|
|
2925
3980
|
for (const block of turn.blocks) {
|
|
2926
|
-
if (block.type !== "system_notification" || !
|
|
3981
|
+
if (block.type !== "system_notification" || !isRecord(block.content)) continue;
|
|
2927
3982
|
const metadata = block.content.metadata;
|
|
2928
|
-
if (!
|
|
3983
|
+
if (!isRecord(metadata)) continue;
|
|
2929
3984
|
const parentId = metadata.parent_fork_tool_call_id;
|
|
2930
3985
|
if (typeof parentId === "string" && parentId.length > 0) return parentId;
|
|
2931
3986
|
}
|
|
@@ -2942,16 +3997,16 @@ function buildMessageContent2(turn) {
|
|
|
2942
3997
|
}
|
|
2943
3998
|
function workspaceNotificationContent(turn) {
|
|
2944
3999
|
const block = turn.blocks.find(
|
|
2945
|
-
(candidate) => candidate.type === "system_notification" &&
|
|
4000
|
+
(candidate) => candidate.type === "system_notification" && isRecord(candidate.content) && candidate.content.notification_type === "workspace_change"
|
|
2946
4001
|
);
|
|
2947
|
-
if (!block || !
|
|
4002
|
+
if (!block || !isRecord(block.content)) return "";
|
|
2948
4003
|
const title = typeof block.content.title === "string" ? block.content.title.trim() : "";
|
|
2949
4004
|
const detail = typeof block.content.detail === "string" ? block.content.detail.trim() : "";
|
|
2950
4005
|
return [title ? `**${title}**` : "", detail].filter(Boolean).join("\n\n");
|
|
2951
4006
|
}
|
|
2952
4007
|
function askUserAnswerContent(turn) {
|
|
2953
4008
|
const answerBlock = turn.blocks.find((block) => block.type === "ask_user_answer");
|
|
2954
|
-
if (!answerBlock || !
|
|
4009
|
+
if (!answerBlock || !isRecord(answerBlock.content)) return null;
|
|
2955
4010
|
const answer = answerBlock.content.answer;
|
|
2956
4011
|
return typeof answer === "string" && answer.trim().length > 0 ? answer : null;
|
|
2957
4012
|
}
|
|
@@ -2971,6 +4026,19 @@ function extractModeFromBlocks(blocks) {
|
|
|
2971
4026
|
return null;
|
|
2972
4027
|
}
|
|
2973
4028
|
function projectionToMessage(turn) {
|
|
4029
|
+
if (turn.kind === "context") {
|
|
4030
|
+
const context = contextProjectionData(turn);
|
|
4031
|
+
if (!context) return null;
|
|
4032
|
+
return {
|
|
4033
|
+
role: "assistant",
|
|
4034
|
+
content: "",
|
|
4035
|
+
kind: "context",
|
|
4036
|
+
context,
|
|
4037
|
+
loop_name: turn.loop_id,
|
|
4038
|
+
entry_id: turn.turn_id,
|
|
4039
|
+
status: turn.status
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
2974
4042
|
if (turn.kind === "compaction" && turn.compaction_id) {
|
|
2975
4043
|
return {
|
|
2976
4044
|
role: "assistant",
|
|
@@ -3141,21 +4209,52 @@ function orderTurns(turns) {
|
|
|
3141
4209
|
return turns;
|
|
3142
4210
|
}
|
|
3143
4211
|
function reconcileOptimisticUserTurns(currentTurns, incomingTurns) {
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
4212
|
+
return reconcileUserTurns(currentTurns, incomingTurns, true);
|
|
4213
|
+
}
|
|
4214
|
+
function reconcileHistoricalUserTurns(currentTurns, incomingTurns) {
|
|
4215
|
+
return reconcileUserTurns(currentTurns, incomingTurns, false);
|
|
4216
|
+
}
|
|
4217
|
+
function reconcileUserTurns(currentTurns, incomingTurns, allowLiveFallback) {
|
|
4218
|
+
const preservedTurns = preserveClientRenderIds(currentTurns, incomingTurns);
|
|
4219
|
+
const optimisticUserTurns = currentTurns.filter(
|
|
4220
|
+
(turn) => turn.role === "user" && (turn.kind ?? "message") === "message" && turn.turn_id.startsWith("local-user-")
|
|
3147
4221
|
);
|
|
3148
|
-
|
|
4222
|
+
const optimisticByRequestId = new Map(
|
|
4223
|
+
optimisticUserTurns.map((turn) => [
|
|
4224
|
+
turn.turn_id.slice("local-user-".length),
|
|
4225
|
+
turn
|
|
4226
|
+
])
|
|
4227
|
+
);
|
|
4228
|
+
const currentTurnIds = new Set(currentTurns.map((turn) => turn.turn_id));
|
|
4229
|
+
const claimedOptimisticIds = /* @__PURE__ */ new Set();
|
|
4230
|
+
const reconciled = preservedTurns.map((turn) => {
|
|
4231
|
+
const exactOptimistic = typeof turn.client_request_id === "string" ? optimisticByRequestId.get(turn.client_request_id) : void 0;
|
|
4232
|
+
if (exactOptimistic && !claimedOptimisticIds.has(exactOptimistic.turn_id) && isUserConfirmation(turn)) {
|
|
4233
|
+
claimedOptimisticIds.add(exactOptimistic.turn_id);
|
|
4234
|
+
return { ...turn, client_render_id: exactOptimistic.turn_id };
|
|
4235
|
+
}
|
|
4236
|
+
const fallbackOptimistic = optimisticUserTurns.find(
|
|
4237
|
+
(candidate) => !claimedOptimisticIds.has(candidate.turn_id)
|
|
4238
|
+
);
|
|
4239
|
+
if (allowLiveFallback && typeof turn.client_request_id !== "string" && fallbackOptimistic && isUserConfirmation(turn) && !turn.turn_id.startsWith("local-user-") && !currentTurnIds.has(turn.turn_id)) {
|
|
4240
|
+
claimedOptimisticIds.add(fallbackOptimistic.turn_id);
|
|
4241
|
+
return { ...turn, client_render_id: fallbackOptimistic.turn_id };
|
|
4242
|
+
}
|
|
4243
|
+
return turn;
|
|
4244
|
+
});
|
|
4245
|
+
return claimedOptimisticIds.size === 0 ? reconciled : reconciled.filter((turn) => !claimedOptimisticIds.has(turn.turn_id));
|
|
4246
|
+
}
|
|
4247
|
+
function isUserConfirmation(turn) {
|
|
4248
|
+
return turn.role === "user" && (turn.kind ?? "message") === "message" || turn.blocks.some((block) => block.type === "ask_user_answer");
|
|
4249
|
+
}
|
|
4250
|
+
function preserveClientRenderIds(currentTurns, incomingTurns) {
|
|
4251
|
+
const currentById = new Map(currentTurns.map((turn) => [turn.turn_id, turn]));
|
|
3149
4252
|
return incomingTurns.map((turn) => {
|
|
3150
4253
|
const current = currentById.get(turn.turn_id);
|
|
3151
4254
|
if (current) {
|
|
3152
4255
|
const currentRenderId = current.client_render_id;
|
|
3153
4256
|
return turn.client_render_id || !currentRenderId ? turn : { ...turn, client_render_id: currentRenderId };
|
|
3154
4257
|
}
|
|
3155
|
-
if (!optimisticUserMatched && optimisticUserTurn && turn.role === "user" && !turn.turn_id.startsWith("local-user-")) {
|
|
3156
|
-
optimisticUserMatched = true;
|
|
3157
|
-
return { ...turn, client_render_id: optimisticUserTurn.turn_id };
|
|
3158
|
-
}
|
|
3159
4258
|
return turn;
|
|
3160
4259
|
});
|
|
3161
4260
|
}
|
|
@@ -3170,7 +4269,7 @@ function areAgentLoopsEqual(left, right) {
|
|
|
3170
4269
|
});
|
|
3171
4270
|
}
|
|
3172
4271
|
function withTurns(state, turns) {
|
|
3173
|
-
const orderedTurns = orderTurns(
|
|
4272
|
+
const orderedTurns = orderTurns(reconcileHistoricalUserTurns(state.turns, turns));
|
|
3174
4273
|
const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
|
|
3175
4274
|
const isWaitingForInput = orderedTurns.some(
|
|
3176
4275
|
(turn) => turn.tool_calls.some((toolCall) => toolCall.status === "awaiting_answer")
|
|
@@ -3188,26 +4287,45 @@ function withTurns(state, turns) {
|
|
|
3188
4287
|
});
|
|
3189
4288
|
const mergedMessages = preservedErrors.length > 0 ? [...messagesWithRenderIds, ...preservedErrors] : messagesWithRenderIds;
|
|
3190
4289
|
const latestMode = [...orderedTurns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
|
|
4290
|
+
const askAnswersFromHistory = extractAskAnswers(orderedTurns);
|
|
4291
|
+
const presentToolCallIds = new Set(
|
|
4292
|
+
orderedTurns.flatMap((turn) => turn.tool_calls.map((toolCall) => toolCall.id))
|
|
4293
|
+
);
|
|
4294
|
+
const askAnswers = { ...askAnswersFromHistory };
|
|
4295
|
+
for (const [toolCallId, answer] of Object.entries(state.askAnswers)) {
|
|
4296
|
+
if (!(toolCallId in askAnswers) && presentToolCallIds.has(toolCallId)) {
|
|
4297
|
+
askAnswers[toolCallId] = answer;
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
3191
4300
|
return {
|
|
3192
4301
|
...state,
|
|
3193
4302
|
turns: orderedTurns,
|
|
3194
4303
|
messages: mergedMessages,
|
|
3195
4304
|
agentLoops: stableAgentLoops,
|
|
3196
4305
|
activeCompaction,
|
|
4306
|
+
askAnswers,
|
|
3197
4307
|
mode: latestMode ?? state.mode,
|
|
3198
4308
|
// 历史恢复时 chat:end(paused) 可能已不在 socket replay 缓存中;
|
|
3199
4309
|
// awaiting_answer 是投影里的持久权威状态,必须据此恢复问答交互。
|
|
3200
4310
|
status: isWaitingForInput ? "waiting_for_input" : state.status
|
|
3201
4311
|
};
|
|
3202
4312
|
}
|
|
4313
|
+
function withQueue(state, snapshot2) {
|
|
4314
|
+
if (!shouldApplySnapshot(state.queue, snapshot2)) return state;
|
|
4315
|
+
return { ...state, queue: snapshot2 };
|
|
4316
|
+
}
|
|
3203
4317
|
var localTurnCounter = 0;
|
|
3204
|
-
function addUserMessage(state, content) {
|
|
4318
|
+
function addUserMessage(state, content, clientRequestId) {
|
|
3205
4319
|
localTurnCounter += 1;
|
|
3206
|
-
const turnId = `local-user-${Date.now()}-${localTurnCounter}`;
|
|
4320
|
+
const turnId = `local-user-${clientRequestId ?? `${Date.now()}-${localTurnCounter}`}`;
|
|
3207
4321
|
const optimisticTurn = {
|
|
3208
4322
|
id: turnId,
|
|
3209
4323
|
sequence: Math.max(0, ...state.turns.map((turn) => turn.sequence)) + 1,
|
|
3210
4324
|
turn_id: turnId,
|
|
4325
|
+
// 乐观态就自赋,跟服务端确认后 reconcileUserTurns 补上的
|
|
4326
|
+
// client_render_id(= 乐观 turn 自己的 turn_id)保持同一个值,
|
|
4327
|
+
// 消费方才能拿它当"确认前后不变"的稳定标识使用。
|
|
4328
|
+
client_render_id: turnId,
|
|
3211
4329
|
loop_id: "root",
|
|
3212
4330
|
role: "user",
|
|
3213
4331
|
status: "streaming",
|
|
@@ -3222,10 +4340,9 @@ function addUserMessage(state, content) {
|
|
|
3222
4340
|
function upsertTurn(state, turn) {
|
|
3223
4341
|
let existing = [...state.turns];
|
|
3224
4342
|
const [nextTurn] = reconcileOptimisticUserTurns(existing, [turn]);
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
);
|
|
4343
|
+
const claimedOptimisticId = nextTurn.client_render_id;
|
|
4344
|
+
if (claimedOptimisticId?.startsWith("local-user-")) {
|
|
4345
|
+
existing = existing.filter((item) => item.turn_id !== claimedOptimisticId);
|
|
3229
4346
|
}
|
|
3230
4347
|
const index = existing.findIndex((item) => item.turn_id === nextTurn.turn_id);
|
|
3231
4348
|
if (index >= 0) {
|
|
@@ -3244,13 +4361,7 @@ function applyTurnPatch(state, patch) {
|
|
|
3244
4361
|
if (lastSequence !== null && patch.sequence <= lastSequence) {
|
|
3245
4362
|
return state;
|
|
3246
4363
|
}
|
|
3247
|
-
|
|
3248
|
-
if (index >= 0) {
|
|
3249
|
-
existing[index] = nextTurn;
|
|
3250
|
-
} else {
|
|
3251
|
-
existing.push(nextTurn);
|
|
3252
|
-
}
|
|
3253
|
-
return withTurns(state, existing);
|
|
4364
|
+
return upsertTurn(state, { ...turn, sequence: patch.sequence });
|
|
3254
4365
|
}
|
|
3255
4366
|
function addErrorMessage(state, content) {
|
|
3256
4367
|
const anchorTurnId = state.turns[state.turns.length - 1]?.turn_id ?? null;
|
|
@@ -3282,19 +4393,27 @@ function markStreamingTurns(state, turnStatus, toolCallStatus) {
|
|
|
3282
4393
|
|
|
3283
4394
|
// src/session/agent-session.ts
|
|
3284
4395
|
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";
|
|
3285
|
-
var
|
|
3286
|
-
|
|
3287
|
-
|
|
4396
|
+
var COMMAND_RESTORE_RETRY_BASE_MS = 1e3;
|
|
4397
|
+
var COMMAND_RESTORE_RETRY_MAX_MS = 3e4;
|
|
4398
|
+
var QUEUE_ACK_TIMEOUT_MS = 1e4;
|
|
4399
|
+
var fallbackRequestCounter = 0;
|
|
4400
|
+
function createClientRequestId() {
|
|
4401
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
4402
|
+
return globalThis.crypto.randomUUID();
|
|
4403
|
+
}
|
|
4404
|
+
fallbackRequestCounter += 1;
|
|
4405
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}-${fallbackRequestCounter}`;
|
|
3288
4406
|
}
|
|
4407
|
+
var OOM_KEYWORDS_RE = /(?:\b(?:exit_code|ExitedWith|exited with)|退出码)\D*(?<!\d)137(?!\d)/i;
|
|
3289
4408
|
function isOomText(value) {
|
|
3290
4409
|
return typeof value === "string" && OOM_KEYWORDS_RE.test(value);
|
|
3291
4410
|
}
|
|
3292
4411
|
function parseJsonRecord(value) {
|
|
3293
|
-
if (
|
|
4412
|
+
if (isRecord(value)) return value;
|
|
3294
4413
|
if (typeof value !== "string") return null;
|
|
3295
4414
|
try {
|
|
3296
4415
|
const parsed = JSON.parse(value);
|
|
3297
|
-
return
|
|
4416
|
+
return isRecord(parsed) ? parsed : null;
|
|
3298
4417
|
} catch {
|
|
3299
4418
|
return null;
|
|
3300
4419
|
}
|
|
@@ -3320,6 +4439,20 @@ function isOomResult(value) {
|
|
|
3320
4439
|
function isToolBridgeContent(value) {
|
|
3321
4440
|
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.action === "string";
|
|
3322
4441
|
}
|
|
4442
|
+
function isCursorInvalidError(error) {
|
|
4443
|
+
return typeof error === "object" && error !== null && "status" in error && error.status === 409;
|
|
4444
|
+
}
|
|
4445
|
+
function queueRequestFailure(error) {
|
|
4446
|
+
return {
|
|
4447
|
+
ok: false,
|
|
4448
|
+
conflict: false,
|
|
4449
|
+
snapshot: null,
|
|
4450
|
+
message: error instanceof Error && error.message ? error.message : "\u961F\u5217\u64CD\u4F5C\u5931\u8D25"
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4453
|
+
function commandDeliveryKey(deliveryId) {
|
|
4454
|
+
return `tool-command:${deliveryId}`;
|
|
4455
|
+
}
|
|
3323
4456
|
function sessionStatusFromChatEnd(status) {
|
|
3324
4457
|
if (status === "paused") return "waiting_for_input";
|
|
3325
4458
|
if (status === "interrupted") return "interrupted";
|
|
@@ -3335,14 +4468,6 @@ function extractTerminalChatEndStatus(events) {
|
|
|
3335
4468
|
}
|
|
3336
4469
|
return null;
|
|
3337
4470
|
}
|
|
3338
|
-
function hasChatRunEvent2(events) {
|
|
3339
|
-
return events.some(
|
|
3340
|
-
(event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
3341
|
-
);
|
|
3342
|
-
}
|
|
3343
|
-
function isUiMetaLike(value) {
|
|
3344
|
-
return isRecord5(value) && ("resourceHTML" in value || "resourceUri" in value || "resourceURI" in value);
|
|
3345
|
-
}
|
|
3346
4471
|
var AgentSession = class _AgentSession {
|
|
3347
4472
|
sessionId;
|
|
3348
4473
|
state;
|
|
@@ -3356,14 +4481,37 @@ var AgentSession = class _AgentSession {
|
|
|
3356
4481
|
patchFlushCancel = null;
|
|
3357
4482
|
joined = false;
|
|
3358
4483
|
joinPending = null;
|
|
4484
|
+
// bootstrap 与每次 reconnect 共用一条订阅生命周期队列。它们都会重置 join
|
|
4485
|
+
// 状态并临时设置 replay source,禁止并发,否则较早操作的 finally 会清掉
|
|
4486
|
+
// 较晚操作仍在使用的来源上下文。
|
|
4487
|
+
subscriptionQueue = Promise.resolve();
|
|
4488
|
+
replaceQueue = Promise.resolve();
|
|
4489
|
+
timelineEpoch = 0;
|
|
4490
|
+
pagingGeneration = 0;
|
|
4491
|
+
liveRevision = 0;
|
|
4492
|
+
modeRevision = 0;
|
|
4493
|
+
liveRevisions = /* @__PURE__ */ new Map();
|
|
4494
|
+
pendingOptimisticTurnIds = /* @__PURE__ */ new Set();
|
|
4495
|
+
olderPending = null;
|
|
4496
|
+
replaceInFlight = 0;
|
|
3359
4497
|
// 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
|
|
3360
4498
|
// 请求响应可能乱序回来,晚到的旧值会把已经退出的会话又显示成回放中;用
|
|
3361
4499
|
// 序号丢弃过期响应又会在失败时留下中间态。串行执行就没有这些情况:每个
|
|
3362
4500
|
// 响应都是当时最新的,失败也只影响自己。
|
|
3363
4501
|
replayQueue = Promise.resolve();
|
|
4502
|
+
// Historical snapshots and live commands share one delivery barrier. A
|
|
4503
|
+
// repair closes the barrier before joining, snapshots the raw command log
|
|
4504
|
+
// after the join, then releases buffered commands in socket arrival order.
|
|
4505
|
+
commandRestoreQueue = Promise.resolve();
|
|
4506
|
+
commandRecoveryGeneration = 0;
|
|
4507
|
+
commandRestoreRetryAttempt = 0;
|
|
4508
|
+
commandRestoreRetryHandle = null;
|
|
4509
|
+
bufferedCommandDeliveries = null;
|
|
3364
4510
|
pendingReplayMessage = null;
|
|
3365
4511
|
pendingReplayMode;
|
|
4512
|
+
pendingReplayKbIds;
|
|
3366
4513
|
recentChatEndAt = 0;
|
|
4514
|
+
subscriptionReplaySource = null;
|
|
3367
4515
|
disposed = false;
|
|
3368
4516
|
getAppContext = null;
|
|
3369
4517
|
sendPending = false;
|
|
@@ -3387,11 +4535,11 @@ var AgentSession = class _AgentSession {
|
|
|
3387
4535
|
const session = new _AgentSession(
|
|
3388
4536
|
sessionId,
|
|
3389
4537
|
{
|
|
3390
|
-
streamTokens: false,
|
|
3391
4538
|
emitEvent: readonlyError,
|
|
3392
4539
|
emitWithAck: async () => readonlyError(),
|
|
3393
4540
|
ensureConnected: readonlyError,
|
|
3394
|
-
|
|
4541
|
+
fetchTurnsPage: async () => ({ turns, nextBefore: null, currentMode: "executing" }),
|
|
4542
|
+
fetchCommands: async () => [],
|
|
3395
4543
|
// 静态历史没有回放身份可言,给个空详情即可,不要抛错——_bootstrap
|
|
3396
4544
|
// 不会跑到这里,但保持只读语义一致。
|
|
3397
4545
|
fetchSessionInfo: async () => ({ replay_state: null }),
|
|
@@ -3415,6 +4563,42 @@ var AgentSession = class _AgentSession {
|
|
|
3415
4563
|
getState() {
|
|
3416
4564
|
return this.state;
|
|
3417
4565
|
}
|
|
4566
|
+
get hasOlderHistory() {
|
|
4567
|
+
return this.state.nextBefore !== null;
|
|
4568
|
+
}
|
|
4569
|
+
/** Load the page immediately preceding the current display window. */
|
|
4570
|
+
loadOlderHistory(options = {}) {
|
|
4571
|
+
if (this.disposed) return Promise.resolve();
|
|
4572
|
+
if (this.olderPending) return this.olderPending;
|
|
4573
|
+
if (this.replaceInFlight > 0) {
|
|
4574
|
+
return this.replaceQueue.then(() => this.loadOlderHistory(options));
|
|
4575
|
+
}
|
|
4576
|
+
const before = this.state.nextBefore;
|
|
4577
|
+
if (!before) return Promise.resolve();
|
|
4578
|
+
const epoch = this.timelineEpoch;
|
|
4579
|
+
const generation = this.pagingGeneration;
|
|
4580
|
+
this.update((state) => ({ ...state, loadingOlder: true }));
|
|
4581
|
+
const request = this.runtime.fetchTurnsPage(this.sessionId, before).then((page) => {
|
|
4582
|
+
if (epoch !== this.timelineEpoch || generation !== this.pagingGeneration || this.state.nextBefore !== before) return;
|
|
4583
|
+
options.beforeCommit?.();
|
|
4584
|
+
this.update((state) => ({
|
|
4585
|
+
...withTurns(state, prependOlder(state.turns, page.turns)),
|
|
4586
|
+
nextBefore: page.nextBefore,
|
|
4587
|
+
loadingOlder: false
|
|
4588
|
+
}));
|
|
4589
|
+
}).catch((error) => {
|
|
4590
|
+
if (isCursorInvalidError(error) && epoch === this.timelineEpoch && generation === this.pagingGeneration && this.state.nextBefore === before) {
|
|
4591
|
+
this.timelineEpoch += 1;
|
|
4592
|
+
return this.replaceFromHistory();
|
|
4593
|
+
}
|
|
4594
|
+
}).finally(() => {
|
|
4595
|
+
if (this.olderPending !== request) return;
|
|
4596
|
+
this.olderPending = null;
|
|
4597
|
+
this.update((state) => ({ ...state, loadingOlder: false }));
|
|
4598
|
+
});
|
|
4599
|
+
this.olderPending = request;
|
|
4600
|
+
return request;
|
|
4601
|
+
}
|
|
3418
4602
|
/** 订阅状态变化。返回取消订阅函数。 */
|
|
3419
4603
|
subscribe(listener) {
|
|
3420
4604
|
this.listeners.add(listener);
|
|
@@ -3467,7 +4651,7 @@ var AgentSession = class _AgentSession {
|
|
|
3467
4651
|
/** 发送一条消息。字符串或多模态内容均可。 */
|
|
3468
4652
|
async send(content, options = {}) {
|
|
3469
4653
|
this.runtime.ensureConnected();
|
|
3470
|
-
if (this.state.isStreaming || this.state.activeCompaction || this.sendPending || this.manualCompactionPending) {
|
|
4654
|
+
if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.sendPending || this.manualCompactionPending) {
|
|
3471
4655
|
this.update(
|
|
3472
4656
|
(s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
|
|
3473
4657
|
);
|
|
@@ -3480,9 +4664,125 @@ var AgentSession = class _AgentSession {
|
|
|
3480
4664
|
this.sendPending = false;
|
|
3481
4665
|
}
|
|
3482
4666
|
}
|
|
4667
|
+
/**
|
|
4668
|
+
* 入队一条等待消息(运行中排队 / 暂停期间照常入队)。
|
|
4669
|
+
*
|
|
4670
|
+
* ack 超时或传输失败时结果未知:用**同一个** `client_request_id` 重试一次,
|
|
4671
|
+
* 服务端按 `(session_id, client_request_id)` 幂等,不会制造第二条。
|
|
4672
|
+
*/
|
|
4673
|
+
async queueMessage(content) {
|
|
4674
|
+
const text = content.trim();
|
|
4675
|
+
if (!text) {
|
|
4676
|
+
return { ok: false, conflict: false, snapshot: null, message: "\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
4677
|
+
}
|
|
4678
|
+
const payload = {
|
|
4679
|
+
session_id: this.sessionId,
|
|
4680
|
+
message: text,
|
|
4681
|
+
client_request_id: createClientRequestId()
|
|
4682
|
+
};
|
|
4683
|
+
try {
|
|
4684
|
+
return await this.requestQueueAck("chat:append", payload);
|
|
4685
|
+
} catch {
|
|
4686
|
+
try {
|
|
4687
|
+
return await this.requestQueueAck("chat:append", payload);
|
|
4688
|
+
} catch (error) {
|
|
4689
|
+
return queueRequestFailure(error);
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4693
|
+
/** 修改一条等待消息的正文;`expectedVersion` 是客户端看到的条目版本。 */
|
|
4694
|
+
async updateQueuedMessage(itemId, expectedVersion, content) {
|
|
4695
|
+
return this.runQueueCommand("chat:queue:update", {
|
|
4696
|
+
session_id: this.sessionId,
|
|
4697
|
+
item_id: itemId,
|
|
4698
|
+
expected_version: expectedVersion,
|
|
4699
|
+
message: content
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4702
|
+
/**
|
|
4703
|
+
* 重排等待消息。`orderedIds` 必须是当前 pending 条目 id 的**完整集合**(顺序任意),
|
|
4704
|
+
* 服务端按 `expectedRevision` 校验后整体替换顺序。
|
|
4705
|
+
*
|
|
4706
|
+
* 拖动排序就该像本地操作一样落地:版本冲突(运行中的任务刚领走一条 / 另一个端也在
|
|
4707
|
+
* 动队列)不甩给用户「刷新后重试」,而是拿冲突响应里那份**最新快照**重算一次再提交
|
|
4708
|
+
* ——掉队的条目(已经被领取执行的)从顺序里剔除,剩下的保持用户拖出来的相对次序。
|
|
4709
|
+
* 最多补两次;仍旧冲突才把结果交回调用方。
|
|
4710
|
+
*/
|
|
4711
|
+
async reorderQueuedMessages(expectedRevision, orderedIds) {
|
|
4712
|
+
let revision = expectedRevision;
|
|
4713
|
+
let ids = orderedIds;
|
|
4714
|
+
let result = await this.runQueueCommand("chat:queue:reorder", {
|
|
4715
|
+
session_id: this.sessionId,
|
|
4716
|
+
expected_revision: revision,
|
|
4717
|
+
ordered_ids: ids
|
|
4718
|
+
});
|
|
4719
|
+
for (let attempt = 0; attempt < 2 && !result.ok && result.conflict; attempt++) {
|
|
4720
|
+
const snapshot2 = result.snapshot;
|
|
4721
|
+
if (!snapshot2) break;
|
|
4722
|
+
const pending = snapshot2.items.filter(canEditQueuedMessage);
|
|
4723
|
+
const live = new Set(pending.map((item) => item.id));
|
|
4724
|
+
const next = ids.filter((id) => live.has(id));
|
|
4725
|
+
for (const item of pending) if (!next.includes(item.id)) next.push(item.id);
|
|
4726
|
+
if (next.length === 0) {
|
|
4727
|
+
return { ok: true, conflict: false, snapshot: snapshot2, message: null };
|
|
4728
|
+
}
|
|
4729
|
+
revision = snapshot2.revision;
|
|
4730
|
+
ids = next;
|
|
4731
|
+
result = await this.runQueueCommand("chat:queue:reorder", {
|
|
4732
|
+
session_id: this.sessionId,
|
|
4733
|
+
expected_revision: revision,
|
|
4734
|
+
ordered_ids: ids
|
|
4735
|
+
});
|
|
4736
|
+
}
|
|
4737
|
+
return result;
|
|
4738
|
+
}
|
|
4739
|
+
/** 删除一条等待消息;不传版本时由服务端自行判定。 */
|
|
4740
|
+
async cancelQueuedMessage(itemId, expectedVersion) {
|
|
4741
|
+
return this.runQueueCommand("chat:queue:cancel", {
|
|
4742
|
+
session_id: this.sessionId,
|
|
4743
|
+
item_id: itemId,
|
|
4744
|
+
expected_version: expectedVersion
|
|
4745
|
+
});
|
|
4746
|
+
}
|
|
4747
|
+
/** 立即补充:把等待消息交给当前运行,在下一个接收点消费,不停止也不另起一轮。 */
|
|
4748
|
+
async deliverQueuedMessage(itemId, expectedVersion) {
|
|
4749
|
+
return this.runQueueCommand("chat:queue:deliver", {
|
|
4750
|
+
session_id: this.sessionId,
|
|
4751
|
+
item_id: itemId,
|
|
4752
|
+
expected_version: expectedVersion
|
|
4753
|
+
});
|
|
4754
|
+
}
|
|
4755
|
+
/** 继续执行:清除暂停,并按当前顺序恢复逐条接续。 */
|
|
4756
|
+
async resumeQueue() {
|
|
4757
|
+
return this.runQueueCommand("chat:queue:resume", { session_id: this.sessionId });
|
|
4758
|
+
}
|
|
4759
|
+
/** 拉取一份权威队列快照。首次连接与每次重连后由实例自动调用,也可以手动刷新。 */
|
|
4760
|
+
async refreshQueue() {
|
|
4761
|
+
return this.runQueueCommand("chat:queue:list", { session_id: this.sessionId });
|
|
4762
|
+
}
|
|
4763
|
+
/**
|
|
4764
|
+
* 发出一条队列命令并安装 ack 里的快照。
|
|
4765
|
+
* 传输层失败(超时 / 断线)会 reject,由调用方决定是否重试。
|
|
4766
|
+
*/
|
|
4767
|
+
async requestQueueAck(event, payload) {
|
|
4768
|
+
this.runtime.ensureConnected();
|
|
4769
|
+
const response = await this.runtime.emitWithAck(event, payload, QUEUE_ACK_TIMEOUT_MS);
|
|
4770
|
+
const result = parseQueueAck(response, this.sessionId);
|
|
4771
|
+
const snapshot2 = result.snapshot;
|
|
4772
|
+
if (snapshot2) this.update((state) => withQueue(state, snapshot2));
|
|
4773
|
+
return result;
|
|
4774
|
+
}
|
|
4775
|
+
/** 队列命令的对外入口:传输失败只体现为失败结果,不向调用方抛异常。 */
|
|
4776
|
+
async runQueueCommand(event, payload) {
|
|
4777
|
+
try {
|
|
4778
|
+
return await this.requestQueueAck(event, payload);
|
|
4779
|
+
} catch (error) {
|
|
4780
|
+
return queueRequestFailure(error);
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
3483
4783
|
async sendUnlocked(content, options) {
|
|
3484
4784
|
await this.ensureIdleBeforeSend();
|
|
3485
|
-
if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionPending) {
|
|
4785
|
+
if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.manualCompactionPending) {
|
|
3486
4786
|
this.update(
|
|
3487
4787
|
(s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
|
|
3488
4788
|
);
|
|
@@ -3492,7 +4792,7 @@ var AgentSession = class _AgentSession {
|
|
|
3492
4792
|
if (this.getAppContext) {
|
|
3493
4793
|
try {
|
|
3494
4794
|
const rawContext = await this.getAppContext();
|
|
3495
|
-
if (!
|
|
4795
|
+
if (!isRecord(rawContext)) {
|
|
3496
4796
|
throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE JSON \u5BF9\u8C61");
|
|
3497
4797
|
}
|
|
3498
4798
|
const serialized = JSON.stringify(rawContext);
|
|
@@ -3500,7 +4800,7 @@ var AgentSession = class _AgentSession {
|
|
|
3500
4800
|
throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE\u53EF\u5E8F\u5217\u5316\u7684 JSON \u5BF9\u8C61");
|
|
3501
4801
|
}
|
|
3502
4802
|
const normalized = JSON.parse(serialized);
|
|
3503
|
-
if (!
|
|
4803
|
+
if (!isRecord(normalized)) {
|
|
3504
4804
|
throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE JSON \u5BF9\u8C61");
|
|
3505
4805
|
}
|
|
3506
4806
|
appContext = normalized;
|
|
@@ -3512,15 +4812,15 @@ var AgentSession = class _AgentSession {
|
|
|
3512
4812
|
}
|
|
3513
4813
|
const askUserAnswer = options.askUserAnswer;
|
|
3514
4814
|
let optimisticTurnId = null;
|
|
4815
|
+
const clientRequestId = createClientRequestId();
|
|
3515
4816
|
let optimisticAskAnswer;
|
|
3516
4817
|
let previousAskAnswer;
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
}
|
|
4818
|
+
this.update((state) => {
|
|
4819
|
+
const next = addUserMessage(state, content, clientRequestId);
|
|
4820
|
+
optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
|
|
4821
|
+
return next;
|
|
4822
|
+
});
|
|
4823
|
+
if (optimisticTurnId) this.markOptimisticTurnPending(optimisticTurnId);
|
|
3524
4824
|
this.update((s) => ({ ...s, isStreaming: true }));
|
|
3525
4825
|
if (askUserAnswer && typeof askUserAnswer.tool_call_id === "string") {
|
|
3526
4826
|
const { tool_call_id, ...rest } = askUserAnswer;
|
|
@@ -3534,17 +4834,22 @@ var AgentSession = class _AgentSession {
|
|
|
3534
4834
|
if (!options.replayDecision) {
|
|
3535
4835
|
this.pendingReplayMessage = content;
|
|
3536
4836
|
this.pendingReplayMode = options.mode;
|
|
4837
|
+
this.pendingReplayKbIds = options.kbIds;
|
|
3537
4838
|
}
|
|
3538
4839
|
const payload = {
|
|
3539
4840
|
session_id: this.sessionId,
|
|
3540
4841
|
message: content,
|
|
4842
|
+
client_request_id: clientRequestId,
|
|
3541
4843
|
mode: options.mode,
|
|
3542
4844
|
askuser_answer: askUserAnswer,
|
|
3543
4845
|
model: options.model,
|
|
3544
4846
|
thinking_override: options.thinkingOverride,
|
|
3545
4847
|
whatif: options.whatif,
|
|
3546
4848
|
replay_decision: options.replayDecision,
|
|
3547
|
-
app_context: appContext
|
|
4849
|
+
app_context: appContext,
|
|
4850
|
+
kb_ids: options.kbIds && options.kbIds.length > 0 ? options.kbIds : void 0,
|
|
4851
|
+
// 运行中提交不必由客户端挑命令:服务端按会话运行状态决定直接执行还是入队。
|
|
4852
|
+
queue_when_running: options.queueWhenRunning === true
|
|
3548
4853
|
};
|
|
3549
4854
|
try {
|
|
3550
4855
|
await this.ensureJoined();
|
|
@@ -3560,6 +4865,21 @@ var AgentSession = class _AgentSession {
|
|
|
3560
4865
|
}
|
|
3561
4866
|
try {
|
|
3562
4867
|
const response = await this.runtime.emitWithAck("chat:send", payload, 3e4);
|
|
4868
|
+
if (response?.status === "queued") {
|
|
4869
|
+
this.discardOptimisticSend(
|
|
4870
|
+
optimisticTurnId,
|
|
4871
|
+
askUserAnswer,
|
|
4872
|
+
optimisticAskAnswer,
|
|
4873
|
+
previousAskAnswer
|
|
4874
|
+
);
|
|
4875
|
+
const snapshot2 = parseQueueAck(response, this.sessionId).snapshot;
|
|
4876
|
+
const running = response.running === true;
|
|
4877
|
+
this.update((current) => {
|
|
4878
|
+
const next = running ? current : { ...current, isStreaming: false };
|
|
4879
|
+
return snapshot2 ? withQueue(next, snapshot2) : next;
|
|
4880
|
+
});
|
|
4881
|
+
return true;
|
|
4882
|
+
}
|
|
3563
4883
|
if (response?.status !== "accepted") {
|
|
3564
4884
|
throw new Error(
|
|
3565
4885
|
typeof response?.message === "string" ? response.message : "\u6D88\u606F\u672A\u88AB\u670D\u52A1\u7AEF\u63A5\u53D7"
|
|
@@ -3578,8 +4898,19 @@ var AgentSession = class _AgentSession {
|
|
|
3578
4898
|
}
|
|
3579
4899
|
}
|
|
3580
4900
|
rollbackOptimisticSend(optimisticTurnId, askUserAnswer, optimisticAskAnswer, previousAskAnswer, message) {
|
|
4901
|
+
this.discardOptimisticSend(
|
|
4902
|
+
optimisticTurnId,
|
|
4903
|
+
askUserAnswer,
|
|
4904
|
+
optimisticAskAnswer,
|
|
4905
|
+
previousAskAnswer
|
|
4906
|
+
);
|
|
4907
|
+
this.update((current) => addErrorMessage({ ...current, isStreaming: false }, message));
|
|
4908
|
+
}
|
|
4909
|
+
/** 撤回一次乐观提交:移除乐观消息、恢复提问作答、清掉回放草稿。 */
|
|
4910
|
+
discardOptimisticSend(optimisticTurnId, askUserAnswer, optimisticAskAnswer, previousAskAnswer) {
|
|
3581
4911
|
this.pendingReplayMessage = null;
|
|
3582
4912
|
this.pendingReplayMode = void 0;
|
|
4913
|
+
this.pendingReplayKbIds = void 0;
|
|
3583
4914
|
this.update((current) => {
|
|
3584
4915
|
let next = optimisticTurnId ? withTurns(
|
|
3585
4916
|
current,
|
|
@@ -3595,18 +4926,20 @@ var AgentSession = class _AgentSession {
|
|
|
3595
4926
|
}
|
|
3596
4927
|
next = { ...next, askAnswers };
|
|
3597
4928
|
}
|
|
3598
|
-
return
|
|
4929
|
+
return next;
|
|
3599
4930
|
});
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
this.runtime.emitEvent("chat:append", { session_id: this.sessionId, message: text });
|
|
3605
|
-
this.update((s) => addUserMessage(s, text));
|
|
4931
|
+
if (optimisticTurnId) {
|
|
4932
|
+
this.pendingOptimisticTurnIds.delete(optimisticTurnId);
|
|
4933
|
+
this.liveRevisions.delete(optimisticTurnId);
|
|
4934
|
+
}
|
|
3606
4935
|
}
|
|
3607
4936
|
/** 停止当前回复。 */
|
|
3608
4937
|
stop() {
|
|
3609
|
-
return this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3).then((response) =>
|
|
4938
|
+
return this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3).then((response) => {
|
|
4939
|
+
const result = response ?? {};
|
|
4940
|
+
if (result.status !== "accepted") this.manualCompactionPending = false;
|
|
4941
|
+
return result;
|
|
4942
|
+
});
|
|
3610
4943
|
}
|
|
3611
4944
|
/** 手动触发上下文压缩。 */
|
|
3612
4945
|
compact() {
|
|
@@ -3626,8 +4959,10 @@ var AgentSession = class _AgentSession {
|
|
|
3626
4959
|
trigger: "manual"
|
|
3627
4960
|
}
|
|
3628
4961
|
}));
|
|
3629
|
-
return this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4).then((response) => response ?? {}).
|
|
4962
|
+
return this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4).then((response) => response ?? {}).catch((error) => {
|
|
3630
4963
|
this.manualCompactionPending = false;
|
|
4964
|
+
throw error;
|
|
4965
|
+
}).finally(() => {
|
|
3631
4966
|
if (this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
|
|
3632
4967
|
this.update((state) => ({ ...state, activeCompaction: null }));
|
|
3633
4968
|
}
|
|
@@ -3666,10 +5001,20 @@ var AgentSession = class _AgentSession {
|
|
|
3666
5001
|
if (this.disposed) return;
|
|
3667
5002
|
if (!this.runtime.onDispose(this)) return;
|
|
3668
5003
|
this.disposed = true;
|
|
5004
|
+
this.timelineEpoch += 1;
|
|
5005
|
+
this.invalidateOlderPaging();
|
|
5006
|
+
this.state = { ...this.state, nextBefore: null, loadingOlder: false };
|
|
3669
5007
|
this.cancelPatchFlush();
|
|
3670
5008
|
this.listeners.clear();
|
|
3671
5009
|
this.emitter.clear();
|
|
3672
5010
|
this.commands.clear();
|
|
5011
|
+
this.commandRecoveryGeneration += 1;
|
|
5012
|
+
this.commandRestoreRetryAttempt = 0;
|
|
5013
|
+
this.bufferedCommandDeliveries = null;
|
|
5014
|
+
if (this.commandRestoreRetryHandle !== null) {
|
|
5015
|
+
globalThis.clearTimeout(this.commandRestoreRetryHandle);
|
|
5016
|
+
this.commandRestoreRetryHandle = null;
|
|
5017
|
+
}
|
|
3673
5018
|
}
|
|
3674
5019
|
get isDisposed() {
|
|
3675
5020
|
return this.disposed;
|
|
@@ -3680,28 +5025,65 @@ var AgentSession = class _AgentSession {
|
|
|
3680
5025
|
if (this.state.connection === connection) return;
|
|
3681
5026
|
this.update((s) => ({ ...s, connection }));
|
|
3682
5027
|
}
|
|
5028
|
+
/**
|
|
5029
|
+
* @internal 服务端广播的权威队列快照。
|
|
5030
|
+
* 结构不合法或 revision 不新的一律丢弃,绝不用旧快照覆盖新状态。
|
|
5031
|
+
*/
|
|
5032
|
+
_handleQueueUpdated(payload) {
|
|
5033
|
+
if (!isSessionQueueSnapshot(payload)) return;
|
|
5034
|
+
const snapshot2 = payload;
|
|
5035
|
+
this.update((state) => withQueue(state, snapshot2));
|
|
5036
|
+
}
|
|
3683
5037
|
/** @internal socket 重连后由 Hub 调用:重新加入房间并补数。 */
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
this.
|
|
3687
|
-
this.
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
this.
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
5038
|
+
_rejoinAfterReconnect() {
|
|
5039
|
+
this.timelineEpoch += 1;
|
|
5040
|
+
this.invalidateOlderPaging();
|
|
5041
|
+
this.update((state) => ({ ...state, nextBefore: null, loadingOlder: false }));
|
|
5042
|
+
const reconnectLiveRevision = this.liveRevision;
|
|
5043
|
+
const reconnectModeRevision = this.modeRevision;
|
|
5044
|
+
const commandRecoveryGeneration = this.beginCommandRecovery();
|
|
5045
|
+
return this.enqueueSubscription(async () => {
|
|
5046
|
+
this.joined = false;
|
|
5047
|
+
this.joinPending = null;
|
|
5048
|
+
try {
|
|
5049
|
+
this.subscriptionReplaySource = "reconnect_replay";
|
|
5050
|
+
try {
|
|
5051
|
+
await this.ensureJoined(reconnectLiveRevision);
|
|
5052
|
+
} finally {
|
|
5053
|
+
this.subscriptionReplaySource = null;
|
|
5054
|
+
this.scheduleHistoricalCommandRestore(commandRecoveryGeneration);
|
|
5055
|
+
}
|
|
5056
|
+
await Promise.all([
|
|
5057
|
+
this.replaceFromHistory({
|
|
5058
|
+
liveRevisionAtStart: reconnectLiveRevision,
|
|
5059
|
+
modeRevisionAtStart: reconnectModeRevision
|
|
5060
|
+
}),
|
|
5061
|
+
this.syncReplayFromSession()
|
|
5062
|
+
]);
|
|
5063
|
+
} catch {
|
|
5064
|
+
}
|
|
5065
|
+
});
|
|
3699
5066
|
}
|
|
3700
5067
|
/** @internal 首次连接:加载历史 + 加入房间。 */
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
5068
|
+
_bootstrap() {
|
|
5069
|
+
const commandRecoveryGeneration = this.beginCommandRecovery();
|
|
5070
|
+
return this.enqueueSubscription(async () => {
|
|
5071
|
+
await this.replaceFromHistory({ suppressErrors: false });
|
|
5072
|
+
this.subscriptionReplaySource = "bootstrap_replay";
|
|
5073
|
+
try {
|
|
5074
|
+
await this.ensureJoined();
|
|
5075
|
+
} finally {
|
|
5076
|
+
this.subscriptionReplaySource = null;
|
|
5077
|
+
}
|
|
5078
|
+
this.scheduleHistoricalCommandRestore(commandRecoveryGeneration);
|
|
5079
|
+
await Promise.all([this.syncReplayFromSession(), this.refreshQueue()]);
|
|
5080
|
+
});
|
|
5081
|
+
}
|
|
5082
|
+
/** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
|
|
5083
|
+
enqueueSubscription(operation) {
|
|
5084
|
+
const run = this.subscriptionQueue.then(operation);
|
|
5085
|
+
this.subscriptionQueue = run.catch(() => void 0);
|
|
5086
|
+
return run;
|
|
3705
5087
|
}
|
|
3706
5088
|
/** 改回放是 owner 专属(服务端 PATCH 走 _get_owned_session)。 */
|
|
3707
5089
|
get canControlReplay() {
|
|
@@ -3758,10 +5140,16 @@ var AgentSession = class _AgentSession {
|
|
|
3758
5140
|
this.applyTurn(turn);
|
|
3759
5141
|
}
|
|
3760
5142
|
/** @internal 历史回放通道(event_format: raw)。 */
|
|
3761
|
-
_handleTurnEvents(events) {
|
|
5143
|
+
_handleTurnEvents(events, replayed = false) {
|
|
3762
5144
|
if (!events.length) return;
|
|
5145
|
+
if (events.some(
|
|
5146
|
+
(event) => event.type === "compaction:end" || event.type === "compaction:error" || event.type === "compaction:abort"
|
|
5147
|
+
)) {
|
|
5148
|
+
this.manualCompactionPending = false;
|
|
5149
|
+
}
|
|
5150
|
+
this.dispatchRawCommands(events);
|
|
3763
5151
|
const terminalChatEndStatus = extractTerminalChatEndStatus(events);
|
|
3764
|
-
if (!terminalChatEndStatus &&
|
|
5152
|
+
if (!terminalChatEndStatus && hasChatRunEvent(events) && this.state.status !== "waiting_for_input") {
|
|
3765
5153
|
this.markRunningIfIdle();
|
|
3766
5154
|
}
|
|
3767
5155
|
if (this.state.turns.length) {
|
|
@@ -3770,7 +5158,10 @@ var AgentSession = class _AgentSession {
|
|
|
3770
5158
|
const updates = this.projectionBuilder.processBatch(events);
|
|
3771
5159
|
for (const update of updates) {
|
|
3772
5160
|
if (update.kind === "upsert") {
|
|
3773
|
-
this.applyTurn(
|
|
5161
|
+
this.applyTurn(
|
|
5162
|
+
update.turn,
|
|
5163
|
+
replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
|
|
5164
|
+
);
|
|
3774
5165
|
} else if (update.kind === "workspace_changed") {
|
|
3775
5166
|
this.emitter.emit("workspaceChanged", {});
|
|
3776
5167
|
}
|
|
@@ -3825,16 +5216,26 @@ var AgentSession = class _AgentSession {
|
|
|
3825
5216
|
}
|
|
3826
5217
|
/** @internal */
|
|
3827
5218
|
_handleRewind(checkpointId) {
|
|
3828
|
-
this.
|
|
3829
|
-
|
|
5219
|
+
this.timelineEpoch += 1;
|
|
5220
|
+
this.invalidateOlderPaging();
|
|
5221
|
+
this.update((s) => ({
|
|
5222
|
+
...s,
|
|
5223
|
+
mode: null,
|
|
5224
|
+
errorMessage: null,
|
|
5225
|
+
nextBefore: null,
|
|
5226
|
+
loadingOlder: false
|
|
5227
|
+
}));
|
|
5228
|
+
void this.replaceFromHistory();
|
|
3830
5229
|
this.emitter.emit("rewind", { checkpointId });
|
|
3831
5230
|
}
|
|
3832
5231
|
/** @internal */
|
|
3833
5232
|
_handleReplayMismatch(data) {
|
|
3834
5233
|
const message = this.pendingReplayMessage ?? data.actual_message ?? "";
|
|
3835
5234
|
const mode = this.pendingReplayMode;
|
|
5235
|
+
const kbIds = this.pendingReplayKbIds;
|
|
3836
5236
|
this.pendingReplayMessage = null;
|
|
3837
5237
|
this.pendingReplayMode = void 0;
|
|
5238
|
+
this.pendingReplayKbIds = void 0;
|
|
3838
5239
|
const respond = (decision) => {
|
|
3839
5240
|
void this.runtime.emitWithAck(
|
|
3840
5241
|
"chat:send",
|
|
@@ -3842,7 +5243,8 @@ var AgentSession = class _AgentSession {
|
|
|
3842
5243
|
session_id: this.sessionId,
|
|
3843
5244
|
message,
|
|
3844
5245
|
mode,
|
|
3845
|
-
replay_decision: decision
|
|
5246
|
+
replay_decision: decision,
|
|
5247
|
+
kb_ids: kbIds && kbIds.length > 0 ? kbIds : void 0
|
|
3846
5248
|
},
|
|
3847
5249
|
3e4
|
|
3848
5250
|
).then((response) => {
|
|
@@ -3883,9 +5285,9 @@ var AgentSession = class _AgentSession {
|
|
|
3883
5285
|
detail,
|
|
3884
5286
|
status,
|
|
3885
5287
|
loopId,
|
|
3886
|
-
metadata:
|
|
5288
|
+
metadata: isRecord(notification.metadata) ? notification.metadata : void 0
|
|
3887
5289
|
});
|
|
3888
|
-
if (notificationType === "bg:started" &&
|
|
5290
|
+
if (notificationType === "bg:started" && isRecord(notification.metadata)) {
|
|
3889
5291
|
const taskId = typeof notification.metadata.task_id === "string" ? notification.metadata.task_id : "";
|
|
3890
5292
|
if (taskId) {
|
|
3891
5293
|
this.emitter.emit("backgroundTask", {
|
|
@@ -3907,12 +5309,12 @@ var AgentSession = class _AgentSession {
|
|
|
3907
5309
|
this.emitter.emit("artifact", event);
|
|
3908
5310
|
}
|
|
3909
5311
|
/** @internal 加入房间的应答里带 is_running,据此校准流式状态。 */
|
|
3910
|
-
_applyRunningState(isRunning) {
|
|
5312
|
+
_applyRunningState(isRunning, liveRevisionAtJoin = this.liveRevision) {
|
|
3911
5313
|
if (isRunning) {
|
|
3912
5314
|
this.update((s) => ({ ...s, status: "running", isStreaming: true }));
|
|
3913
5315
|
} else if (this.state.isStreaming) {
|
|
3914
5316
|
this.update((s) => ({ ...s, isStreaming: false }));
|
|
3915
|
-
void this.
|
|
5317
|
+
void this.replaceFromHistory({ liveRevisionAtStart: liveRevisionAtJoin });
|
|
3916
5318
|
}
|
|
3917
5319
|
}
|
|
3918
5320
|
// ---------------------------------------------------------------- 内部实现
|
|
@@ -3933,14 +5335,36 @@ var AgentSession = class _AgentSession {
|
|
|
3933
5335
|
completeChat(status) {
|
|
3934
5336
|
this.finishStreaming(status);
|
|
3935
5337
|
this.recentChatEndAt = Date.now();
|
|
3936
|
-
void this.
|
|
5338
|
+
void this.replaceFromHistory();
|
|
3937
5339
|
this.emitter.emit("chatEnd", { status });
|
|
3938
5340
|
}
|
|
3939
|
-
|
|
5341
|
+
markTurnFresh(turnId) {
|
|
5342
|
+
this.liveRevision += 1;
|
|
5343
|
+
this.liveRevisions.set(turnId, this.liveRevision);
|
|
5344
|
+
}
|
|
5345
|
+
markOptimisticTurnPending(turnId) {
|
|
5346
|
+
this.pendingOptimisticTurnIds.add(turnId);
|
|
5347
|
+
this.markTurnFresh(turnId);
|
|
5348
|
+
}
|
|
5349
|
+
pruneOptimisticLiveRevisions() {
|
|
5350
|
+
const retained = new Set(
|
|
5351
|
+
this.state.turns.map((turn) => turn.turn_id).filter((turnId) => turnId.startsWith("local-user-"))
|
|
5352
|
+
);
|
|
5353
|
+
for (const turnId of this.liveRevisions.keys()) {
|
|
5354
|
+
if (turnId.startsWith("local-user-") && !retained.has(turnId)) {
|
|
5355
|
+
this.pendingOptimisticTurnIds.delete(turnId);
|
|
5356
|
+
this.liveRevisions.delete(turnId);
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
applyTurn(turn, source = "live") {
|
|
3940
5361
|
this.flushPatches();
|
|
5362
|
+
this.markTurnFresh(turn.turn_id);
|
|
5363
|
+
if (extractModeFromBlocks(turn.blocks)) this.modeRevision += 1;
|
|
3941
5364
|
const previous = this.state;
|
|
3942
5365
|
this.update((s) => upsertTurn(s, turn));
|
|
3943
|
-
this.
|
|
5366
|
+
this.pruneOptimisticLiveRevisions();
|
|
5367
|
+
this.emitTurnDerivedEvents(previous, turn, source);
|
|
3944
5368
|
}
|
|
3945
5369
|
schedulePatchFlush() {
|
|
3946
5370
|
if (this.patchFlushHandle != null) return;
|
|
@@ -3973,11 +5397,15 @@ var AgentSession = class _AgentSession {
|
|
|
3973
5397
|
this.pendingTurnPatches.clear();
|
|
3974
5398
|
for (const patch of patches) {
|
|
3975
5399
|
const previous = this.state;
|
|
3976
|
-
|
|
5400
|
+
const next = applyTurnPatch(previous, patch);
|
|
5401
|
+
if (next === previous) continue;
|
|
5402
|
+
this.liveRevision += 1;
|
|
5403
|
+
this.liveRevisions.set(patch.turn_id, this.liveRevision);
|
|
3977
5404
|
const turn = patch.data.turn;
|
|
3978
|
-
if (turn)
|
|
3979
|
-
|
|
3980
|
-
|
|
5405
|
+
if (turn && extractModeFromBlocks(turn.blocks)) this.modeRevision += 1;
|
|
5406
|
+
this.setState(next);
|
|
5407
|
+
this.pruneOptimisticLiveRevisions();
|
|
5408
|
+
if (turn) this.emitTurnDerivedEvents(previous, turn);
|
|
3981
5409
|
}
|
|
3982
5410
|
if (this.patchFlushCancel) {
|
|
3983
5411
|
this.patchFlushCancel();
|
|
@@ -3986,7 +5414,7 @@ var AgentSession = class _AgentSession {
|
|
|
3986
5414
|
}
|
|
3987
5415
|
}
|
|
3988
5416
|
/** turn 数据落地后统一派生语义事件(指令 / 工具调用 / 消息 / 模式 / 预览 / OOM)。 */
|
|
3989
|
-
emitTurnDerivedEvents(previous, turn) {
|
|
5417
|
+
emitTurnDerivedEvents(previous, turn, source = "live") {
|
|
3990
5418
|
const mode = extractModeFromBlocks(turn.blocks);
|
|
3991
5419
|
if (mode && previous.mode !== mode) {
|
|
3992
5420
|
this.emitter.emit("modeChange", { mode });
|
|
@@ -3999,23 +5427,24 @@ var AgentSession = class _AgentSession {
|
|
|
3999
5427
|
this.dispatchCommand(turn, block);
|
|
4000
5428
|
continue;
|
|
4001
5429
|
}
|
|
4002
|
-
if (block.type === "tool_ui" && block.tool_call_id &&
|
|
5430
|
+
if (block.type === "tool_ui" && block.tool_call_id && isToolUiCard(block.content)) {
|
|
4003
5431
|
const ui = block.content;
|
|
4004
5432
|
if (ui.target !== "preview") continue;
|
|
4005
|
-
const
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
const
|
|
4009
|
-
if (this.deliveredToolPreviews.get(key) ===
|
|
4010
|
-
this.deliveredToolPreviews.set(key,
|
|
5433
|
+
const resolved = resolveToolUiCardContent(ui);
|
|
5434
|
+
if (!resolved) continue;
|
|
5435
|
+
const key = buildToolUiCardKey(block.tool_call_id, resolved.type, resolved.content);
|
|
5436
|
+
const fingerprint = JSON.stringify([resolved.content, ui.mcpApp?.sourceId]);
|
|
5437
|
+
if (this.deliveredToolPreviews.get(key) === fingerprint) continue;
|
|
5438
|
+
this.deliveredToolPreviews.set(key, fingerprint);
|
|
4011
5439
|
this.emitter.emit("toolPreview", {
|
|
4012
5440
|
toolCallId: block.tool_call_id,
|
|
4013
|
-
type:
|
|
4014
|
-
content:
|
|
4015
|
-
title: ui.title ?? "\u5DE5\u5177\u9884\u89C8"
|
|
5441
|
+
type: resolved.type,
|
|
5442
|
+
content: resolved.content,
|
|
5443
|
+
title: ui.title ?? "\u5DE5\u5177\u9884\u89C8",
|
|
5444
|
+
...ui.mcpApp ? { mcpApp: ui.mcpApp } : {}
|
|
4016
5445
|
});
|
|
4017
5446
|
}
|
|
4018
|
-
if (block.type === "system_notification" &&
|
|
5447
|
+
if (block.type === "system_notification" && isRecord(block.content)) {
|
|
4019
5448
|
this._handleSystemNotification(turn.loop_id, block.content);
|
|
4020
5449
|
}
|
|
4021
5450
|
}
|
|
@@ -4035,7 +5464,7 @@ var AgentSession = class _AgentSession {
|
|
|
4035
5464
|
const finished = toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled";
|
|
4036
5465
|
if (finished && !this.finishedToolCallIds.has(toolCall.id)) {
|
|
4037
5466
|
this.finishedToolCallIds.add(toolCall.id);
|
|
4038
|
-
this.emitter.emit("toolResult", { toolCall: info, turn });
|
|
5467
|
+
this.emitter.emit("toolResult", { toolCall: info, turn, source });
|
|
4039
5468
|
if (isOomResult(toolCall.result)) {
|
|
4040
5469
|
this.notifySandboxOom(toolCall.id);
|
|
4041
5470
|
}
|
|
@@ -4052,17 +5481,36 @@ var AgentSession = class _AgentSession {
|
|
|
4052
5481
|
dispatchCommand(turn, block) {
|
|
4053
5482
|
if (!isToolBridgeContent(block.content)) return;
|
|
4054
5483
|
const { action, payload } = block.content;
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
5484
|
+
const deliveryKey = block.delivery_id ? commandDeliveryKey(block.delivery_id) : `legacy-tool-command:${turn.turn_id}:${block.tool_call_id ?? ""}:${action}:${JSON.stringify(payload)}`;
|
|
5485
|
+
this.deliverCommand(deliveryKey, action, payload, block.tool_call_id ?? void 0);
|
|
5486
|
+
}
|
|
5487
|
+
dispatchRawCommands(events) {
|
|
5488
|
+
for (const event of events) {
|
|
5489
|
+
if (event.type !== "loop:tool_bridge") continue;
|
|
5490
|
+
const deliveryId = String(event.payload.delivery_id ?? "");
|
|
5491
|
+
const bridge = event.payload.bridge;
|
|
5492
|
+
if (!deliveryId || !isToolBridgeContent(bridge)) continue;
|
|
5493
|
+
this.deliverCommand(
|
|
5494
|
+
commandDeliveryKey(deliveryId),
|
|
5495
|
+
bridge.action,
|
|
5496
|
+
bridge.payload,
|
|
5497
|
+
String(event.payload.tool_call_id ?? "") || void 0
|
|
5498
|
+
);
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
deliverCommand(deliveryKey, action, payload, toolCallId) {
|
|
5502
|
+
if (this.bufferedCommandDeliveries) {
|
|
5503
|
+
this.bufferedCommandDeliveries.push({ deliveryKey, action, payload, toolCallId });
|
|
5504
|
+
return;
|
|
4060
5505
|
}
|
|
4061
|
-
|
|
5506
|
+
this.deliverCommandNow({ deliveryKey, action, payload, toolCallId });
|
|
5507
|
+
}
|
|
5508
|
+
deliverCommandNow(command) {
|
|
5509
|
+
const { deliveryKey, action, payload, toolCallId } = command;
|
|
4062
5510
|
if (this.deliveredCommandKeys.has(deliveryKey)) return;
|
|
4063
5511
|
this.deliveredCommandKeys.add(deliveryKey);
|
|
4064
|
-
const meta = { toolCallId
|
|
4065
|
-
this.emitter.emit("command", { action, payload, toolCallId
|
|
5512
|
+
const meta = { toolCallId };
|
|
5513
|
+
this.emitter.emit("command", { action, payload, toolCallId });
|
|
4066
5514
|
this.commands.dispatch(action, payload, meta);
|
|
4067
5515
|
}
|
|
4068
5516
|
notifySandboxOom(key) {
|
|
@@ -4071,49 +5519,146 @@ var AgentSession = class _AgentSession {
|
|
|
4071
5519
|
this.update((s) => addErrorMessage(s, OOM_MESSAGE));
|
|
4072
5520
|
this.emitter.emit("sandboxOom", { toolCallId: key });
|
|
4073
5521
|
}
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
const
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
5522
|
+
replaceFromHistory(options = {}) {
|
|
5523
|
+
if (this.disposed) return Promise.resolve();
|
|
5524
|
+
const operationEpoch = this.timelineEpoch;
|
|
5525
|
+
const liveRevisionAtStart = options.liveRevisionAtStart ?? this.liveRevision;
|
|
5526
|
+
const modeRevisionAtStart = options.modeRevisionAtStart ?? this.modeRevision;
|
|
5527
|
+
this.invalidateOlderPaging();
|
|
5528
|
+
this.replaceInFlight += 1;
|
|
5529
|
+
const operation = async () => {
|
|
5530
|
+
let previousNextBefore = null;
|
|
5531
|
+
try {
|
|
5532
|
+
if (this.disposed || operationEpoch !== this.timelineEpoch) return;
|
|
5533
|
+
this.invalidateOlderPaging();
|
|
5534
|
+
previousNextBefore = this.state.nextBefore;
|
|
5535
|
+
this.update((state) => ({ ...state, nextBefore: null, loadingOlder: false }));
|
|
5536
|
+
const page = await this.runtime.fetchTurnsPage(this.sessionId);
|
|
5537
|
+
if (this.disposed || operationEpoch !== this.timelineEpoch) return;
|
|
5538
|
+
if (!this.joined) this.seedToolCallDeliveryBaseline(page.turns);
|
|
5539
|
+
this.update((state) => {
|
|
5540
|
+
const next = withTurns(
|
|
5541
|
+
state,
|
|
5542
|
+
replaceWindow(state.turns, page.turns, liveRevisionAtStart, {
|
|
5543
|
+
current: this.liveRevision,
|
|
5544
|
+
byTurnId: this.liveRevisions
|
|
5545
|
+
})
|
|
5546
|
+
);
|
|
5547
|
+
return {
|
|
5548
|
+
...next,
|
|
5549
|
+
mode: this.modeRevision === modeRevisionAtStart ? page.currentMode : state.mode,
|
|
5550
|
+
nextBefore: page.nextBefore,
|
|
5551
|
+
loadingOlder: false
|
|
5552
|
+
};
|
|
5553
|
+
});
|
|
5554
|
+
this.pruneOptimisticLiveRevisions();
|
|
5555
|
+
this.projectionBuilder._seedTurns(this.state.turns);
|
|
5556
|
+
} catch (error) {
|
|
5557
|
+
if (!this.disposed && operationEpoch === this.timelineEpoch) {
|
|
5558
|
+
this.update((state) => ({
|
|
5559
|
+
...state,
|
|
5560
|
+
nextBefore: previousNextBefore,
|
|
5561
|
+
loadingOlder: false
|
|
5562
|
+
}));
|
|
4090
5563
|
}
|
|
4091
|
-
|
|
4092
|
-
}
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
5564
|
+
if (options.suppressErrors === false) throw error;
|
|
5565
|
+
} finally {
|
|
5566
|
+
this.replaceInFlight -= 1;
|
|
5567
|
+
}
|
|
5568
|
+
};
|
|
5569
|
+
const run = this.replaceQueue.then(operation);
|
|
5570
|
+
this.replaceQueue = run.catch(() => void 0);
|
|
5571
|
+
return run;
|
|
5572
|
+
}
|
|
5573
|
+
invalidateOlderPaging() {
|
|
5574
|
+
this.pagingGeneration += 1;
|
|
5575
|
+
this.olderPending = null;
|
|
5576
|
+
}
|
|
5577
|
+
beginCommandRecovery() {
|
|
5578
|
+
this.commandRecoveryGeneration += 1;
|
|
5579
|
+
this.commandRestoreRetryAttempt = 0;
|
|
5580
|
+
this.bufferedCommandDeliveries ??= [];
|
|
5581
|
+
if (this.commandRestoreRetryHandle !== null) {
|
|
5582
|
+
globalThis.clearTimeout(this.commandRestoreRetryHandle);
|
|
5583
|
+
this.commandRestoreRetryHandle = null;
|
|
4096
5584
|
}
|
|
5585
|
+
return this.commandRecoveryGeneration;
|
|
4097
5586
|
}
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
5587
|
+
scheduleHistoricalCommandRestore(generation) {
|
|
5588
|
+
const restore = async () => {
|
|
5589
|
+
if (this.disposed || generation !== this.commandRecoveryGeneration) return;
|
|
5590
|
+
try {
|
|
5591
|
+
const commands = await this.runtime.fetchCommands(this.sessionId);
|
|
5592
|
+
if (this.disposed) return;
|
|
5593
|
+
for (const command of commands) {
|
|
5594
|
+
if (this.disposed) return;
|
|
5595
|
+
this.deliverCommandNow({
|
|
5596
|
+
deliveryKey: commandDeliveryKey(command.deliveryId),
|
|
5597
|
+
action: command.action,
|
|
5598
|
+
payload: command.payload,
|
|
5599
|
+
toolCallId: command.toolCallId
|
|
5600
|
+
});
|
|
5601
|
+
}
|
|
5602
|
+
if (generation !== this.commandRecoveryGeneration) return;
|
|
5603
|
+
this.commandRestoreRetryAttempt = 0;
|
|
5604
|
+
this.flushBufferedCommands(generation);
|
|
5605
|
+
} catch {
|
|
5606
|
+
if (!this.disposed && generation === this.commandRecoveryGeneration && this.commandRestoreRetryHandle === null) {
|
|
5607
|
+
const retryDelay = Math.min(
|
|
5608
|
+
COMMAND_RESTORE_RETRY_BASE_MS * 2 ** this.commandRestoreRetryAttempt,
|
|
5609
|
+
COMMAND_RESTORE_RETRY_MAX_MS
|
|
5610
|
+
);
|
|
5611
|
+
this.commandRestoreRetryAttempt += 1;
|
|
5612
|
+
this.commandRestoreRetryHandle = globalThis.setTimeout(() => {
|
|
5613
|
+
this.commandRestoreRetryHandle = null;
|
|
5614
|
+
if (!this.disposed && generation === this.commandRecoveryGeneration) {
|
|
5615
|
+
this.scheduleHistoricalCommandRestore(generation);
|
|
5616
|
+
}
|
|
5617
|
+
}, retryDelay);
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5620
|
+
};
|
|
5621
|
+
const run = this.commandRestoreQueue.then(restore);
|
|
5622
|
+
this.commandRestoreQueue = run.catch(() => void 0);
|
|
5623
|
+
}
|
|
5624
|
+
flushBufferedCommands(generation) {
|
|
5625
|
+
const pending = [...this.bufferedCommandDeliveries ?? []];
|
|
5626
|
+
this.bufferedCommandDeliveries = [];
|
|
5627
|
+
let index = 0;
|
|
5628
|
+
while (index < pending.length) {
|
|
5629
|
+
if (this.disposed) return;
|
|
5630
|
+
if (generation !== this.commandRecoveryGeneration) {
|
|
5631
|
+
const currentGenerationBuffer = this.bufferedCommandDeliveries ?? [];
|
|
5632
|
+
this.bufferedCommandDeliveries = [
|
|
5633
|
+
...pending.slice(index),
|
|
5634
|
+
...currentGenerationBuffer
|
|
5635
|
+
];
|
|
5636
|
+
return;
|
|
5637
|
+
}
|
|
5638
|
+
this.deliverCommandNow(pending[index]);
|
|
5639
|
+
index += 1;
|
|
5640
|
+
const reentrantBuffer = this.bufferedCommandDeliveries;
|
|
5641
|
+
if (index === pending.length && reentrantBuffer && reentrantBuffer.length > 0) {
|
|
5642
|
+
pending.push(...reentrantBuffer);
|
|
5643
|
+
this.bufferedCommandDeliveries = [];
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
if (!this.disposed && generation === this.commandRecoveryGeneration) {
|
|
5647
|
+
this.bufferedCommandDeliveries = null;
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
/** 首次订阅前的 REST 历史建立语义事件基线,随后同一工具的订阅回放不再投递。 */
|
|
5651
|
+
seedToolCallDeliveryBaseline(turns) {
|
|
4108
5652
|
for (const turn of turns) {
|
|
4109
|
-
for (const
|
|
4110
|
-
|
|
4111
|
-
|
|
5653
|
+
for (const toolCall of turn.tool_calls) {
|
|
5654
|
+
this.seenToolCallIds.add(toolCall.id);
|
|
5655
|
+
if (toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled") {
|
|
5656
|
+
this.finishedToolCallIds.add(toolCall.id);
|
|
4112
5657
|
}
|
|
4113
5658
|
}
|
|
4114
5659
|
}
|
|
4115
5660
|
}
|
|
4116
|
-
ensureJoined() {
|
|
5661
|
+
ensureJoined(liveRevisionBoundary = this.liveRevision) {
|
|
4117
5662
|
if (this.joined) return Promise.resolve();
|
|
4118
5663
|
if (this.joinPending) return this.joinPending;
|
|
4119
5664
|
const join = this.runtime.emitWithAck(
|
|
@@ -4121,8 +5666,7 @@ var AgentSession = class _AgentSession {
|
|
|
4121
5666
|
{
|
|
4122
5667
|
session_id: this.sessionId,
|
|
4123
5668
|
event_format: "raw",
|
|
4124
|
-
resume: true
|
|
4125
|
-
stream_tokens: this.runtime.streamTokens
|
|
5669
|
+
resume: true
|
|
4126
5670
|
},
|
|
4127
5671
|
5e3
|
|
4128
5672
|
).then((response) => {
|
|
@@ -4132,7 +5676,7 @@ var AgentSession = class _AgentSession {
|
|
|
4132
5676
|
);
|
|
4133
5677
|
}
|
|
4134
5678
|
this.joined = true;
|
|
4135
|
-
this._applyRunningState(Boolean(response?.is_running));
|
|
5679
|
+
this._applyRunningState(Boolean(response?.is_running), liveRevisionBoundary);
|
|
4136
5680
|
}).finally(() => {
|
|
4137
5681
|
this.joinPending = null;
|
|
4138
5682
|
});
|
|
@@ -4140,6 +5684,7 @@ var AgentSession = class _AgentSession {
|
|
|
4140
5684
|
return join;
|
|
4141
5685
|
}
|
|
4142
5686
|
async ensureIdleBeforeSend() {
|
|
5687
|
+
if (this.state.isStreaming) return;
|
|
4143
5688
|
if (Date.now() - this.recentChatEndAt > 3e3) return;
|
|
4144
5689
|
const deadline = Date.now() + 3e3;
|
|
4145
5690
|
while (Date.now() < deadline) {
|
|
@@ -4174,6 +5719,7 @@ var SessionHub = class {
|
|
|
4174
5719
|
connectionListeners = /* @__PURE__ */ new Set();
|
|
4175
5720
|
bound = false;
|
|
4176
5721
|
hasConnected = false;
|
|
5722
|
+
authBusyReconnect = new AuthBusyReconnect();
|
|
4177
5723
|
constructor(client) {
|
|
4178
5724
|
this.client = client;
|
|
4179
5725
|
}
|
|
@@ -4195,16 +5741,28 @@ var SessionHub = class {
|
|
|
4195
5741
|
* 只有最后一份归还才退订房间——否则 StrictMode 双跑、路由抖动这类"连两次
|
|
4196
5742
|
* 只释放一次"的场景会把仍在用的会话退订掉,之后所有 turn/chat 事件静默丢失。
|
|
4197
5743
|
*/
|
|
4198
|
-
async connect(sessionId) {
|
|
5744
|
+
async connect(sessionId, options = {}) {
|
|
4199
5745
|
const existing = this.sessions.get(sessionId);
|
|
4200
5746
|
if (existing && !existing.isDisposed) {
|
|
4201
5747
|
this.hold(sessionId);
|
|
5748
|
+
try {
|
|
5749
|
+
options.setup?.(existing);
|
|
5750
|
+
} catch (error) {
|
|
5751
|
+
existing.dispose();
|
|
5752
|
+
throw error;
|
|
5753
|
+
}
|
|
4202
5754
|
return this.bootstrapPending.get(sessionId) ?? existing;
|
|
4203
5755
|
}
|
|
4204
5756
|
this.ensureBound();
|
|
4205
5757
|
const session = new AgentSession(sessionId, this.runtimeFor(), this.connection);
|
|
4206
5758
|
this.sessions.set(sessionId, session);
|
|
4207
5759
|
this.hold(sessionId);
|
|
5760
|
+
try {
|
|
5761
|
+
options.setup?.(session);
|
|
5762
|
+
} catch (error) {
|
|
5763
|
+
session.dispose();
|
|
5764
|
+
throw error;
|
|
5765
|
+
}
|
|
4208
5766
|
this.ensureConnected();
|
|
4209
5767
|
const bootstrap = session._bootstrap().then(() => session);
|
|
4210
5768
|
this.bootstrapPending.set(sessionId, bootstrap);
|
|
@@ -4228,6 +5786,7 @@ var SessionHub = class {
|
|
|
4228
5786
|
/** 手动重连(例如登录态变化后)。 */
|
|
4229
5787
|
reconnect() {
|
|
4230
5788
|
this.ensureBound();
|
|
5789
|
+
this.authBusyReconnect.cancel();
|
|
4231
5790
|
const socket = this.client.socket();
|
|
4232
5791
|
socket.disconnect();
|
|
4233
5792
|
this.setConnection("connecting");
|
|
@@ -4235,11 +5794,13 @@ var SessionHub = class {
|
|
|
4235
5794
|
}
|
|
4236
5795
|
/** 断开连接(不清理 session 实例)。 */
|
|
4237
5796
|
disconnect() {
|
|
5797
|
+
this.authBusyReconnect.cancel();
|
|
4238
5798
|
this.client.socket().disconnect();
|
|
4239
5799
|
this.setConnection("disconnected");
|
|
4240
5800
|
}
|
|
4241
5801
|
/** 清理当前身份下缓存的全部会话,避免切换登录身份后复用旧快照。 */
|
|
4242
5802
|
disposeAll() {
|
|
5803
|
+
this.authBusyReconnect.cancel();
|
|
4243
5804
|
this.bootstrapPending.clear();
|
|
4244
5805
|
for (const session of [...this.sessions.values()]) {
|
|
4245
5806
|
this.forceRelease(session);
|
|
@@ -4251,6 +5812,7 @@ var SessionHub = class {
|
|
|
4251
5812
|
* turn:* / chat:* 事件都会静默丢弃。
|
|
4252
5813
|
*/
|
|
4253
5814
|
_handleSocketReplaced() {
|
|
5815
|
+
this.authBusyReconnect.cancel();
|
|
4254
5816
|
this.bound = false;
|
|
4255
5817
|
this.setConnection("disconnected");
|
|
4256
5818
|
if (this.sessions.size === 0) return;
|
|
@@ -4259,7 +5821,6 @@ var SessionHub = class {
|
|
|
4259
5821
|
}
|
|
4260
5822
|
runtimeFor() {
|
|
4261
5823
|
return {
|
|
4262
|
-
streamTokens: this.client.options?.streamTokens !== false,
|
|
4263
5824
|
emitEvent: (event, data) => {
|
|
4264
5825
|
;
|
|
4265
5826
|
this.client.socket().emit(event, data);
|
|
@@ -4275,7 +5836,8 @@ var SessionHub = class {
|
|
|
4275
5836
|
});
|
|
4276
5837
|
}),
|
|
4277
5838
|
ensureConnected: () => this.ensureConnected(),
|
|
4278
|
-
|
|
5839
|
+
fetchTurnsPage: (sessionId, before) => this.client.sessions.getSessionTurnsPage(sessionId, { before }),
|
|
5840
|
+
fetchCommands: (sessionId) => this.client.sessions.getSessionTurnCommands(sessionId),
|
|
4279
5841
|
fetchSessionInfo: (sessionId) => this.client.sessions.getSession(sessionId),
|
|
4280
5842
|
updateReplay: (sessionId, payload) => this.client.sessions.updateReplaySession(sessionId, payload).then((result) => result.replay_state ?? null),
|
|
4281
5843
|
listDir: (sessionId, dirPath) => this.client.sessions.listDir(sessionId, dirPath),
|
|
@@ -4303,6 +5865,9 @@ var SessionHub = class {
|
|
|
4303
5865
|
if (this.sessions.get(session.sessionId) === session) {
|
|
4304
5866
|
this.sessions.delete(session.sessionId);
|
|
4305
5867
|
}
|
|
5868
|
+
if (this.sessions.size === 0) {
|
|
5869
|
+
this.authBusyReconnect.cancel();
|
|
5870
|
+
}
|
|
4306
5871
|
;
|
|
4307
5872
|
this.client.socket().emit("session:unsubscribe", { session_id: session.sessionId });
|
|
4308
5873
|
return true;
|
|
@@ -4311,10 +5876,13 @@ var SessionHub = class {
|
|
|
4311
5876
|
forceRelease(session) {
|
|
4312
5877
|
this.holdCounts.delete(session.sessionId);
|
|
4313
5878
|
session.dispose();
|
|
5879
|
+
if (this.sessions.size === 0) {
|
|
5880
|
+
this.authBusyReconnect.cancel();
|
|
5881
|
+
}
|
|
4314
5882
|
}
|
|
4315
5883
|
ensureConnected() {
|
|
4316
5884
|
const socket = this.client.socket();
|
|
4317
|
-
if (socket.connected || socket.active) return;
|
|
5885
|
+
if (socket.connected || socket.active || this.authBusyReconnect.pending) return;
|
|
4318
5886
|
this.setConnection("connecting");
|
|
4319
5887
|
socket.connect();
|
|
4320
5888
|
}
|
|
@@ -4346,17 +5914,35 @@ var SessionHub = class {
|
|
|
4346
5914
|
this.bound = true;
|
|
4347
5915
|
const socket = this.client.socket();
|
|
4348
5916
|
socket.on("connect", () => {
|
|
5917
|
+
this.authBusyReconnect.cancel();
|
|
4349
5918
|
const isReconnect = this.hasConnected;
|
|
4350
5919
|
this.hasConnected = true;
|
|
4351
5920
|
this.setConnection("connected");
|
|
4352
5921
|
if (isReconnect) {
|
|
4353
5922
|
for (const session of this.sessions.values()) {
|
|
4354
5923
|
void session._rejoinAfterReconnect();
|
|
5924
|
+
void session.refreshQueue();
|
|
4355
5925
|
}
|
|
4356
5926
|
}
|
|
4357
5927
|
});
|
|
4358
|
-
socket.on("connect_error", () =>
|
|
4359
|
-
|
|
5928
|
+
socket.on("connect_error", (error) => {
|
|
5929
|
+
this.setConnection("disconnected");
|
|
5930
|
+
if (this.sessions.size === 0) {
|
|
5931
|
+
this.authBusyReconnect.cancel();
|
|
5932
|
+
return;
|
|
5933
|
+
}
|
|
5934
|
+
if (this.authBusyReconnect.schedule(error, () => {
|
|
5935
|
+
const current = this.client.socket();
|
|
5936
|
+
if (current !== socket || current.connected || current.active) return;
|
|
5937
|
+
this.setConnection("connecting");
|
|
5938
|
+
current.connect();
|
|
5939
|
+
})) return;
|
|
5940
|
+
this.authBusyReconnect.cancel();
|
|
5941
|
+
});
|
|
5942
|
+
socket.on("disconnect", () => {
|
|
5943
|
+
this.authBusyReconnect.cancel();
|
|
5944
|
+
this.setConnection("disconnected");
|
|
5945
|
+
});
|
|
4360
5946
|
socket.io.on("reconnect_attempt", () => this.setConnection("reconnecting"));
|
|
4361
5947
|
socket.on("turn:start", (data) => {
|
|
4362
5948
|
for (const session of this.route(data.session_id)) session._handleTurnStart(data);
|
|
@@ -4367,9 +5953,25 @@ var SessionHub = class {
|
|
|
4367
5953
|
socket.on("turn:end", (data) => {
|
|
4368
5954
|
for (const session of this.route(data.session_id)) session._handleTurnEnd(data);
|
|
4369
5955
|
});
|
|
4370
|
-
socket.on(
|
|
4371
|
-
|
|
4372
|
-
|
|
5956
|
+
socket.on(
|
|
5957
|
+
"turn:timing",
|
|
5958
|
+
(data) => {
|
|
5959
|
+
for (const session of this.route(data.session_id)) {
|
|
5960
|
+
session._handleTurnEvents([
|
|
5961
|
+
{ type: "llm:first_token:delivered", payload: data }
|
|
5962
|
+
]);
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
);
|
|
5966
|
+
socket.on(
|
|
5967
|
+
"turn:events",
|
|
5968
|
+
(data, acknowledge) => {
|
|
5969
|
+
acknowledgeTurnEvents(acknowledge);
|
|
5970
|
+
for (const session of this.route(data.session_id)) {
|
|
5971
|
+
session._handleTurnEvents(data.events ?? [], data.replayed === true);
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
);
|
|
4373
5975
|
socket.on("chat:start", (data) => {
|
|
4374
5976
|
for (const session of this.route(data?.session_id)) session._handleChatStart();
|
|
4375
5977
|
});
|
|
@@ -4381,6 +5983,9 @@ var SessionHub = class {
|
|
|
4381
5983
|
socket.on("system:error", (data) => {
|
|
4382
5984
|
for (const session of this.route(data.session_id)) session._handleSystemError(data.message);
|
|
4383
5985
|
});
|
|
5986
|
+
socket.on("chat:queue:updated", (data) => {
|
|
5987
|
+
for (const session of this.route(data?.session_id)) session._handleQueueUpdated(data);
|
|
5988
|
+
});
|
|
4384
5989
|
socket.on(
|
|
4385
5990
|
"system:notification",
|
|
4386
5991
|
(data) => {
|
|
@@ -4497,21 +6102,14 @@ function resolveAuthToken(options) {
|
|
|
4497
6102
|
return token ? token : null;
|
|
4498
6103
|
}
|
|
4499
6104
|
|
|
4500
|
-
// src/version.ts
|
|
4501
|
-
var SDK_NAME = "agent-client";
|
|
4502
|
-
var SDK_VERSION = true ? "2610.0.0-beta.7" : "1.1.1";
|
|
4503
|
-
|
|
4504
6105
|
// src/socket.ts
|
|
4505
|
-
function withSdkIdentity(auth) {
|
|
4506
|
-
return { ...auth ?? {}, sdk: SDK_NAME, sdk_version: SDK_VERSION };
|
|
4507
|
-
}
|
|
4508
6106
|
function createSocket(options) {
|
|
4509
6107
|
const token = resolveAuthToken(options);
|
|
4510
6108
|
return io(options.baseUrl, {
|
|
4511
6109
|
path: options.path ?? "/socket.io",
|
|
4512
6110
|
withCredentials: true,
|
|
4513
6111
|
query: token ? { token } : void 0,
|
|
4514
|
-
auth: typeof options.token === "function" ? (cb) => cb(
|
|
6112
|
+
auth: typeof options.token === "function" ? (cb) => cb(buildSocketAuth(options) ?? {}) : buildSocketAuth(options) ?? {},
|
|
4515
6113
|
autoConnect: false
|
|
4516
6114
|
});
|
|
4517
6115
|
}
|
|
@@ -4527,8 +6125,17 @@ var BladeClient = class {
|
|
|
4527
6125
|
options;
|
|
4528
6126
|
auth;
|
|
4529
6127
|
headless;
|
|
6128
|
+
computers;
|
|
4530
6129
|
models;
|
|
4531
6130
|
sessions;
|
|
6131
|
+
/** 按用户读取的插件目录与图标;没有会话时也能用。 */
|
|
6132
|
+
plugins;
|
|
6133
|
+
chatGroups;
|
|
6134
|
+
/**
|
|
6135
|
+
* 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
|
|
6136
|
+
* 只有内置 Web 的同源页面使用,第三方宿主页面不会被自动统计。
|
|
6137
|
+
*/
|
|
6138
|
+
siteVisits;
|
|
4532
6139
|
/** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
|
|
4533
6140
|
hub;
|
|
4534
6141
|
constructor(options) {
|
|
@@ -4538,8 +6145,12 @@ var BladeClient = class {
|
|
|
4538
6145
|
};
|
|
4539
6146
|
this.auth = new AuthResource(this);
|
|
4540
6147
|
this.headless = new HeadlessResource(this);
|
|
6148
|
+
this.computers = new ComputersResource(this);
|
|
4541
6149
|
this.models = new ModelsResource(this);
|
|
4542
6150
|
this.sessions = new SessionsResource(this);
|
|
6151
|
+
this.plugins = new PluginsResource(this);
|
|
6152
|
+
this.chatGroups = new ChatGroupsResource(this);
|
|
6153
|
+
this.siteVisits = new SiteVisitsResource(this);
|
|
4543
6154
|
this.hub = new SessionHub(this);
|
|
4544
6155
|
if (this.options.token === void 0) {
|
|
4545
6156
|
this.runtimeToken = readStoredToken(this.options.baseUrl);
|
|
@@ -4710,7 +6321,7 @@ var BladeClient = class {
|
|
|
4710
6321
|
return response;
|
|
4711
6322
|
}
|
|
4712
6323
|
buildAuthedUrl(path) {
|
|
4713
|
-
const url =
|
|
6324
|
+
const url = buildBackendUrl(this.options.baseUrl, path);
|
|
4714
6325
|
const token = this.resolveTokenForUrl(url);
|
|
4715
6326
|
if (token) {
|
|
4716
6327
|
url.searchParams.set("token", token);
|
|
@@ -4729,14 +6340,11 @@ var BladeClient = class {
|
|
|
4729
6340
|
return result;
|
|
4730
6341
|
}
|
|
4731
6342
|
buildUrl(path) {
|
|
4732
|
-
return
|
|
6343
|
+
return buildBackendUrl(this.options.baseUrl, path);
|
|
4733
6344
|
}
|
|
4734
6345
|
baseUrlWithTrailingSlash() {
|
|
4735
6346
|
return this.options.baseUrl.endsWith("/") ? this.options.baseUrl : `${this.options.baseUrl}/`;
|
|
4736
6347
|
}
|
|
4737
|
-
toBaseRelativePath(path) {
|
|
4738
|
-
return path.startsWith("/") ? path.slice(1) : path;
|
|
4739
|
-
}
|
|
4740
6348
|
isSameBackendUrl(url) {
|
|
4741
6349
|
const base = new URL(this.baseUrlWithTrailingSlash());
|
|
4742
6350
|
if (url.origin !== base.origin) {
|
|
@@ -4892,6 +6500,126 @@ function parseXhrHeaders(rawHeaders) {
|
|
|
4892
6500
|
return headers;
|
|
4893
6501
|
}
|
|
4894
6502
|
|
|
6503
|
+
// src/polling-backoff.ts
|
|
6504
|
+
var MAX_BACKOFF_MS = 6e4;
|
|
6505
|
+
var MAX_TIMER_MS = 2147483647;
|
|
6506
|
+
var PollingBackoff = class {
|
|
6507
|
+
constructor(intervalMs) {
|
|
6508
|
+
this.intervalMs = intervalMs;
|
|
6509
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0 || intervalMs > MAX_BACKOFF_MS) {
|
|
6510
|
+
throw new RangeError("Polling interval must be between 0 and 60000 ms");
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
6513
|
+
intervalMs;
|
|
6514
|
+
failures = 0;
|
|
6515
|
+
nextAt = 0;
|
|
6516
|
+
reset() {
|
|
6517
|
+
this.failures = 0;
|
|
6518
|
+
this.nextAt = 0;
|
|
6519
|
+
}
|
|
6520
|
+
failed(error) {
|
|
6521
|
+
if (error instanceof Error && error.name === "AbortError" || error instanceof BladeApiError && error.status < 500 && error.status !== 408 && error.status !== 429) {
|
|
6522
|
+
this.nextAt = false;
|
|
6523
|
+
return;
|
|
6524
|
+
}
|
|
6525
|
+
this.failures = Math.min(this.failures + 1, 10);
|
|
6526
|
+
const ceiling = Math.min(MAX_BACKOFF_MS, this.intervalMs * 2 ** this.failures);
|
|
6527
|
+
const backoff = Math.max(this.intervalMs, ceiling * (0.5 + Math.random() * 0.5));
|
|
6528
|
+
const header = error instanceof BladeApiError ? error.response.headers.get("Retry-After")?.trim() : void 0;
|
|
6529
|
+
const retryAfter = header ? /^\d+$/.test(header) ? Number(header) * 1e3 : Date.parse(header) - Date.now() : 0;
|
|
6530
|
+
const delay = Math.max(backoff, Number.isNaN(retryAfter) ? 0 : retryAfter);
|
|
6531
|
+
this.nextAt = delay > MAX_TIMER_MS ? false : Date.now() + delay;
|
|
6532
|
+
}
|
|
6533
|
+
/** Zero means ready, false means stop, otherwise wait at least this many ms. */
|
|
6534
|
+
waitMs() {
|
|
6535
|
+
return this.nextAt === false ? false : Math.max(0, this.nextAt - Date.now());
|
|
6536
|
+
}
|
|
6537
|
+
};
|
|
6538
|
+
|
|
6539
|
+
// src/platform-endpoints.ts
|
|
6540
|
+
var PLATFORM_SERVICE_NAMES = [
|
|
6541
|
+
"os",
|
|
6542
|
+
"hub",
|
|
6543
|
+
"agent",
|
|
6544
|
+
"oauth",
|
|
6545
|
+
"llmGateway",
|
|
6546
|
+
"gitea",
|
|
6547
|
+
"tile"
|
|
6548
|
+
];
|
|
6549
|
+
var EMPTY_PLATFORM_ENDPOINTS = Object.freeze({
|
|
6550
|
+
services: Object.freeze({})
|
|
6551
|
+
});
|
|
6552
|
+
function defaultDocumentBaseUrl() {
|
|
6553
|
+
if (typeof document === "undefined") return void 0;
|
|
6554
|
+
if (document.querySelector("base[href]")) return document.baseURI;
|
|
6555
|
+
if (typeof location !== "undefined" && location.origin) return `${location.origin}/`;
|
|
6556
|
+
return document.baseURI;
|
|
6557
|
+
}
|
|
6558
|
+
function normalizeServiceUrl(value) {
|
|
6559
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
6560
|
+
if (value.includes("?") || value.includes("#")) return null;
|
|
6561
|
+
try {
|
|
6562
|
+
const url = new URL(value.trim());
|
|
6563
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
6564
|
+
if (url.username || url.password || url.search || url.hash) return null;
|
|
6565
|
+
return url.toString().replace(/\/$/, "");
|
|
6566
|
+
} catch {
|
|
6567
|
+
return null;
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
function parsePlatformEndpoints(value) {
|
|
6571
|
+
if (!isRecord(value) || !isRecord(value.services)) return null;
|
|
6572
|
+
const services = {};
|
|
6573
|
+
for (const name of PLATFORM_SERVICE_NAMES) {
|
|
6574
|
+
if (!(name in value.services)) continue;
|
|
6575
|
+
const url = normalizeServiceUrl(value.services[name]);
|
|
6576
|
+
if (!url) return null;
|
|
6577
|
+
if (name === "agent" && new URL(url).pathname !== "/") return null;
|
|
6578
|
+
services[name] = url;
|
|
6579
|
+
}
|
|
6580
|
+
return { services };
|
|
6581
|
+
}
|
|
6582
|
+
async function loadPlatformEndpoints(options = {}) {
|
|
6583
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
6584
|
+
const baseUrl = options.baseUrl ?? defaultDocumentBaseUrl();
|
|
6585
|
+
if (!fetchImpl || !baseUrl) return EMPTY_PLATFORM_ENDPOINTS;
|
|
6586
|
+
const controller = new AbortController();
|
|
6587
|
+
const timeout = globalThis.setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
|
|
6588
|
+
try {
|
|
6589
|
+
const response = await fetchImpl(new URL("config.json", baseUrl), {
|
|
6590
|
+
cache: "no-store",
|
|
6591
|
+
credentials: "same-origin",
|
|
6592
|
+
headers: { Accept: "application/json" },
|
|
6593
|
+
signal: controller.signal
|
|
6594
|
+
});
|
|
6595
|
+
if (!response.ok) return EMPTY_PLATFORM_ENDPOINTS;
|
|
6596
|
+
return parsePlatformEndpoints(await response.json()) ?? EMPTY_PLATFORM_ENDPOINTS;
|
|
6597
|
+
} catch {
|
|
6598
|
+
return EMPTY_PLATFORM_ENDPOINTS;
|
|
6599
|
+
} finally {
|
|
6600
|
+
globalThis.clearTimeout(timeout);
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
function resolveServiceUrl(endpoints, name, path = "") {
|
|
6604
|
+
const baseUrl = endpoints.services[name];
|
|
6605
|
+
if (!baseUrl) return null;
|
|
6606
|
+
if (!path) return baseUrl;
|
|
6607
|
+
const pathReference = path.trimStart();
|
|
6608
|
+
if (path.includes("\\") || pathReference.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(pathReference)) {
|
|
6609
|
+
return null;
|
|
6610
|
+
}
|
|
6611
|
+
const base = new URL(`${baseUrl}/`);
|
|
6612
|
+
const target = new URL(path.replace(/^\/+/, ""), base);
|
|
6613
|
+
if (target.origin !== base.origin || !target.pathname.startsWith(base.pathname)) {
|
|
6614
|
+
return null;
|
|
6615
|
+
}
|
|
6616
|
+
return target.toString();
|
|
6617
|
+
}
|
|
6618
|
+
|
|
6619
|
+
// src/version.ts
|
|
6620
|
+
var SDK_NAME = "agent-client";
|
|
6621
|
+
var SDK_VERSION = true ? "2610.0.0-beta.70" : "1.1.1";
|
|
6622
|
+
|
|
4895
6623
|
// src/commands/protocol.ts
|
|
4896
6624
|
function isCommandEnvelope(value) {
|
|
4897
6625
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -4992,12 +6720,19 @@ var Task = type2({
|
|
|
4992
6720
|
});
|
|
4993
6721
|
export {
|
|
4994
6722
|
AgentSession,
|
|
6723
|
+
AuthBusyReconnect,
|
|
4995
6724
|
BladeApiError,
|
|
4996
6725
|
BladeClient,
|
|
6726
|
+
ChatGroupsResource,
|
|
4997
6727
|
ClientProjectionBuilder,
|
|
6728
|
+
ComputersResource,
|
|
4998
6729
|
DEFAULT_REPLAY_SPEED,
|
|
6730
|
+
EMPTY_PLATFORM_ENDPOINTS,
|
|
6731
|
+
EMPTY_SESSION_QUEUE,
|
|
4999
6732
|
LayoutType,
|
|
5000
6733
|
ModelsResource,
|
|
6734
|
+
PluginsResource,
|
|
6735
|
+
PollingBackoff,
|
|
5001
6736
|
SDK_NAME,
|
|
5002
6737
|
SDK_VERSION,
|
|
5003
6738
|
SessionHub,
|
|
@@ -5007,22 +6742,57 @@ export {
|
|
|
5007
6742
|
Task,
|
|
5008
6743
|
TaskStatus,
|
|
5009
6744
|
acceptedPostChatFollowupCompletesLatestRun,
|
|
6745
|
+
acknowledgeTurnEvents,
|
|
6746
|
+
authBusyRetryDelayMs,
|
|
5010
6747
|
buildMessageContent,
|
|
6748
|
+
buildToolUiCardKey,
|
|
6749
|
+
canCancelQueuedMessage,
|
|
6750
|
+
canDeliverQueuedMessage,
|
|
6751
|
+
canEditQueuedMessage,
|
|
6752
|
+
canToggleComputer,
|
|
6753
|
+
chatErrorForDisplay,
|
|
6754
|
+
collectInlineToolUiCards,
|
|
6755
|
+
collectPreviewToolUiCards,
|
|
6756
|
+
computerDaemonVersion,
|
|
6757
|
+
computerOS,
|
|
6758
|
+
computerPlatformLabel,
|
|
6759
|
+
computerState,
|
|
5011
6760
|
connectEmbedded,
|
|
5012
6761
|
contentPreview,
|
|
6762
|
+
contextProjectionData,
|
|
5013
6763
|
createInitialSessionState,
|
|
5014
6764
|
createSocket,
|
|
5015
6765
|
extractTextAttachments,
|
|
6766
|
+
getContextDisplayState,
|
|
6767
|
+
getContextGroupDisplayState,
|
|
5016
6768
|
getFileParts,
|
|
5017
6769
|
getImageParts,
|
|
5018
6770
|
getTextContent,
|
|
6771
|
+
groupAdjacentContextRuns,
|
|
5019
6772
|
groupMessagesByLoop,
|
|
6773
|
+
hasChatRunEvent,
|
|
6774
|
+
isAppDevToolUiCard,
|
|
5020
6775
|
isCommandEnvelope,
|
|
5021
6776
|
isHiddenInternalMessage,
|
|
5022
6777
|
isInboundEnvelope,
|
|
6778
|
+
isInternalStatusToolUiCard,
|
|
6779
|
+
isMcpAppData,
|
|
6780
|
+
isSessionQueueSnapshot,
|
|
6781
|
+
isToolUiCard,
|
|
5023
6782
|
latestPostChatFollowup,
|
|
6783
|
+
loadPlatformEndpoints,
|
|
5024
6784
|
normalizeMessageContent,
|
|
6785
|
+
parseQueueAck,
|
|
6786
|
+
prependOlder,
|
|
6787
|
+
queuePauseReasonLabel,
|
|
6788
|
+
queuedMessageStatusLabel,
|
|
6789
|
+
reconcileHistoricalUserTurns,
|
|
5025
6790
|
reconcileOptimisticUserTurns,
|
|
6791
|
+
replaceWindow,
|
|
6792
|
+
resolveServiceUrl,
|
|
6793
|
+
resolveToolUiCardContent,
|
|
6794
|
+
shouldApplySnapshot,
|
|
6795
|
+
sortComputers,
|
|
5026
6796
|
toReplaySnapshot,
|
|
5027
6797
|
transformSlashCommand
|
|
5028
6798
|
};
|