@blade-hq/agent-client 2610.0.0-beta.4 → 2610.0.0-beta.40
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 +94 -6
- package/dist/auth.d.ts +0 -1
- package/dist/blade-client.d.ts +2 -2
- package/dist/index.d.ts +15 -4
- package/dist/index.js +1263 -209
- package/dist/index.js.map +1 -1
- package/dist/platform-endpoints.d.ts +14 -0
- package/dist/resources/computers.d.ts +85 -0
- package/dist/resources/models.d.ts +2 -0
- package/dist/resources/sessions.d.ts +19 -1
- package/dist/schemas/context.d.ts +49 -0
- package/dist/schemas/event.d.ts +9 -0
- package/dist/schemas/message-utils.d.ts +5 -1
- package/dist/schemas/message.d.ts +5 -0
- package/dist/schemas/projection.d.ts +22 -1
- package/dist/schemas/session.d.ts +7 -1
- package/dist/session/agent-session.d.ts +53 -16
- package/dist/session/events.d.ts +2 -0
- package/dist/session/history-page.d.ts +9 -0
- package/dist/session/hub.d.ts +8 -1
- package/dist/session/state.d.ts +11 -2
- package/dist/shared/projection/builder.d.ts +9 -0
- package/dist/shared/projection/context.d.ts +3 -0
- package/dist/shared/projection/helpers.d.ts +1 -1
- 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 +3 -0
- package/dist/shared/type-guards.d.ts +2 -0
- package/dist/types/socket-events.d.ts +30 -4
- package/package.json +1 -1
- package/public-api.md +318 -7
package/dist/index.js
CHANGED
|
@@ -214,6 +214,11 @@ var AuthResource = class {
|
|
|
214
214
|
}
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
+
// src/shared/type-guards.ts
|
|
218
|
+
function isRecord(value) {
|
|
219
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
220
|
+
}
|
|
221
|
+
|
|
217
222
|
// src/resources/headless.ts
|
|
218
223
|
var HeadlessError = class extends Error {
|
|
219
224
|
constructor(message, detail) {
|
|
@@ -352,8 +357,7 @@ var HeadlessResource = class {
|
|
|
352
357
|
socket.on("system:error", onSystemError);
|
|
353
358
|
socket.emit("session:subscribe", {
|
|
354
359
|
session_id,
|
|
355
|
-
event_format: "raw"
|
|
356
|
-
stream_tokens: this.client.options.streamTokens !== false
|
|
360
|
+
event_format: "raw"
|
|
357
361
|
});
|
|
358
362
|
socket.emit(
|
|
359
363
|
"chat:send",
|
|
@@ -395,9 +399,6 @@ function errorMessageFromResult(result) {
|
|
|
395
399
|
}
|
|
396
400
|
return null;
|
|
397
401
|
}
|
|
398
|
-
function isRecord(value) {
|
|
399
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
400
|
-
}
|
|
401
402
|
function ensureSocketConnected(socket, timeoutMs, detail) {
|
|
402
403
|
if (socket.connected) {
|
|
403
404
|
return Promise.resolve();
|
|
@@ -425,6 +426,66 @@ function ensureSocketConnected(socket, timeoutMs, detail) {
|
|
|
425
426
|
});
|
|
426
427
|
}
|
|
427
428
|
|
|
429
|
+
// src/resources/computers.ts
|
|
430
|
+
function computerState(computer) {
|
|
431
|
+
if (computer.is_primary) return "primary";
|
|
432
|
+
if (computer.enabled) return "enabled";
|
|
433
|
+
return computer.online ? "idle" : "offline";
|
|
434
|
+
}
|
|
435
|
+
function computerOS(computer) {
|
|
436
|
+
const os = (computer.os ?? "").toLowerCase();
|
|
437
|
+
if (os.startsWith("darwin") || os.startsWith("mac")) return "macos";
|
|
438
|
+
if (os.startsWith("windows")) return "windows";
|
|
439
|
+
if (os.startsWith("linux")) return "linux";
|
|
440
|
+
return "unknown";
|
|
441
|
+
}
|
|
442
|
+
function computerPlatformLabel(computer) {
|
|
443
|
+
const os = (computer.os ?? "").trim();
|
|
444
|
+
const arch = (computer.arch ?? "").trim();
|
|
445
|
+
if (os && arch) return `${os}/${arch}`;
|
|
446
|
+
return os || arch || "";
|
|
447
|
+
}
|
|
448
|
+
function computerDaemonVersion(computer) {
|
|
449
|
+
return (computer.daemon_version ?? "").trim();
|
|
450
|
+
}
|
|
451
|
+
function canToggleComputer(computer) {
|
|
452
|
+
return !computer.is_primary;
|
|
453
|
+
}
|
|
454
|
+
function sortComputers(computers) {
|
|
455
|
+
return [...computers].sort((a, b) => {
|
|
456
|
+
const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
|
|
457
|
+
if (byCreated !== 0) return byCreated;
|
|
458
|
+
return a.label.localeCompare(b.label);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
var ComputersResource = class {
|
|
462
|
+
constructor(client) {
|
|
463
|
+
this.client = client;
|
|
464
|
+
}
|
|
465
|
+
client;
|
|
466
|
+
/**
|
|
467
|
+
* 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
|
|
468
|
+
* 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
|
|
469
|
+
*/
|
|
470
|
+
list(sessionId) {
|
|
471
|
+
return this.client.json(
|
|
472
|
+
"GET",
|
|
473
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
|
|
478
|
+
* 电脑离线时同样能登记,等它上线就直接可用。
|
|
479
|
+
*/
|
|
480
|
+
setEnabled(sessionId, computer, enabled) {
|
|
481
|
+
return this.client.json(
|
|
482
|
+
"PUT",
|
|
483
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
|
|
484
|
+
{ enabled }
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
428
489
|
// src/resources/models.ts
|
|
429
490
|
var ModelsResource = class {
|
|
430
491
|
constructor(client) {
|
|
@@ -473,7 +534,10 @@ var SessionInfo = type({
|
|
|
473
534
|
"disable_tools?": "string[]",
|
|
474
535
|
"runtime_type?": "string | null",
|
|
475
536
|
"daemon_id?": "string | null",
|
|
537
|
+
"agent_runtime_id?": "string | null",
|
|
476
538
|
"workspace_path?": "string | null",
|
|
539
|
+
"ba_version?": "string | null",
|
|
540
|
+
"sandbox_version?": "string | null",
|
|
477
541
|
"match?": "unknown"
|
|
478
542
|
});
|
|
479
543
|
|
|
@@ -539,7 +603,7 @@ function upsertToolCall(state, toolCallId, toolName, displayName, args) {
|
|
|
539
603
|
});
|
|
540
604
|
}
|
|
541
605
|
}
|
|
542
|
-
function applyToolResult(state, toolCallId, result, status, durationMs, sourceLoop) {
|
|
606
|
+
function applyToolResult(state, toolCallId, result, status, durationMs, ranInParallel, sourceLoop) {
|
|
543
607
|
let toolName = "";
|
|
544
608
|
let displayName = "";
|
|
545
609
|
let finalResult = result;
|
|
@@ -551,6 +615,7 @@ function applyToolResult(state, toolCallId, result, status, durationMs, sourceLo
|
|
|
551
615
|
}
|
|
552
616
|
const update = { status, result: finalResult };
|
|
553
617
|
if (durationMs != null) update.duration_ms = durationMs;
|
|
618
|
+
if (ranInParallel != null) update.ran_in_parallel = ranInParallel;
|
|
554
619
|
state.toolCalls[i] = { ...tc, ...update };
|
|
555
620
|
toolName = tc.tool_name;
|
|
556
621
|
displayName = tc.display_name;
|
|
@@ -732,11 +797,17 @@ function buildCompactionTurn(sequence, compactionId, loopId, status, data) {
|
|
|
732
797
|
summary_preview: summaryPreview,
|
|
733
798
|
summary_full: summaryFull,
|
|
734
799
|
archived_count: archivedCount,
|
|
800
|
+
archived_files: Array.isArray(data.archived_files) ? data.archived_files.filter(
|
|
801
|
+
(item) => typeof item === "object" && item !== null && !Array.isArray(item)
|
|
802
|
+
) : null,
|
|
803
|
+
archived_tool_calls: Array.isArray(data.archived_tool_calls) ? data.archived_tool_calls.filter(
|
|
804
|
+
(item) => typeof item === "object" && item !== null && !Array.isArray(item)
|
|
805
|
+
) : null,
|
|
735
806
|
tokens_before: tokensBefore,
|
|
736
807
|
tokens_after: tokensAfter,
|
|
737
808
|
saved_ratio: savedRatio,
|
|
738
809
|
trigger: asTrigger(data.trigger),
|
|
739
|
-
failure_reason:
|
|
810
|
+
failure_reason: [data.failure_reason, data.fallback_reason, data.reason].find((value) => typeof value === "string" && value.length > 0) ?? null,
|
|
740
811
|
fallback_applied: typeof data.fallback_applied === "boolean" ? data.fallback_applied : null
|
|
741
812
|
};
|
|
742
813
|
}
|
|
@@ -991,6 +1062,9 @@ function createTurnState(turnId, loopId, opts = {}) {
|
|
|
991
1062
|
toolCalls: [],
|
|
992
1063
|
usage: null,
|
|
993
1064
|
responseDone: false,
|
|
1065
|
+
firstTokenMs: null,
|
|
1066
|
+
streamDurationMs: null,
|
|
1067
|
+
firstTokenDeliveryMs: null,
|
|
994
1068
|
memoryRefs: opts.memoryRefs ?? null,
|
|
995
1069
|
parentForkToolCallId: null
|
|
996
1070
|
};
|
|
@@ -1009,6 +1083,9 @@ function snapshot(state, sequence, status) {
|
|
|
1009
1083
|
model: state.model,
|
|
1010
1084
|
usage: state.usage ? { ...state.usage } : null,
|
|
1011
1085
|
duration_ms: Math.max(0, Date.now() - state.startedAt),
|
|
1086
|
+
first_token_ms: state.firstTokenMs,
|
|
1087
|
+
stream_duration_ms: state.streamDurationMs,
|
|
1088
|
+
first_token_delivery_ms: state.firstTokenDeliveryMs,
|
|
1012
1089
|
started_at: new Date(state.startedAt).toISOString(),
|
|
1013
1090
|
context_window: state.contextWindow,
|
|
1014
1091
|
memory_refs: state.memoryRefs,
|
|
@@ -1023,10 +1100,130 @@ function toolStatusFromResult(result) {
|
|
|
1023
1100
|
return inferToolStatus(text);
|
|
1024
1101
|
}
|
|
1025
1102
|
|
|
1103
|
+
// src/schemas/context.ts
|
|
1104
|
+
var BUILTIN_CONTEXT_TITLES = {
|
|
1105
|
+
"agent-instructions": "\u9879\u76EE\u8BF4\u660E",
|
|
1106
|
+
"skill-catalog": "\u53EF\u7528\u80FD\u529B",
|
|
1107
|
+
"runtime-context": "\u5F53\u524D\u5DE5\u4F5C\u73AF\u5883",
|
|
1108
|
+
"workspace-context": "\u5F53\u524D\u5DE5\u4F5C\u533A\u73AF\u5883"
|
|
1109
|
+
};
|
|
1110
|
+
var BUILTIN_CONTEXT_DETAILS = {
|
|
1111
|
+
"agent-instructions": "\u8FD9\u4E9B\u8BF4\u660E\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u7406\u89E3\u5F53\u524D\u9879\u76EE\u548C\u5DE5\u4F5C\u8981\u6C42\u3002",
|
|
1112
|
+
"skill-catalog": "\u8FD9\u4E9B\u80FD\u529B\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u9009\u62E9\u5408\u9002\u7684\u65B9\u5F0F\u5B8C\u6210\u4EFB\u52A1\u3002",
|
|
1113
|
+
"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",
|
|
1114
|
+
"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"
|
|
1115
|
+
};
|
|
1116
|
+
function nonEmptyString(value) {
|
|
1117
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1118
|
+
}
|
|
1119
|
+
function contextProjectionData(fields) {
|
|
1120
|
+
const contextKind = nonEmptyString(fields.context_kind);
|
|
1121
|
+
const contextKey = nonEmptyString(fields.context_key);
|
|
1122
|
+
const contextName = nonEmptyString(fields.context_name);
|
|
1123
|
+
const contextRevision = nonEmptyString(fields.context_revision);
|
|
1124
|
+
if (!contextKind || !contextKey || !contextName || !contextRevision || typeof fields.context_order !== "number" || !Number.isFinite(fields.context_order) || !fields.context_action) {
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
context_kind: contextKind,
|
|
1129
|
+
context_key: contextKey,
|
|
1130
|
+
context_name: contextName,
|
|
1131
|
+
context_order: fields.context_order,
|
|
1132
|
+
context_action: fields.context_action,
|
|
1133
|
+
context_revision: contextRevision,
|
|
1134
|
+
display_title: nonEmptyString(fields.display_title),
|
|
1135
|
+
display_summary: nonEmptyString(fields.display_summary),
|
|
1136
|
+
sources: Array.isArray(fields.sources) ? fields.sources : []
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
function getContextDisplayState(context) {
|
|
1140
|
+
const title = nonEmptyString(context.display_title) ?? BUILTIN_CONTEXT_TITLES[context.context_kind] ?? "\u5BF9\u8BDD\u80CC\u666F";
|
|
1141
|
+
const isRemoved = context.context_action === "removed";
|
|
1142
|
+
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");
|
|
1143
|
+
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";
|
|
1144
|
+
return { title, summary, detail, isRemoved };
|
|
1145
|
+
}
|
|
1146
|
+
function groupAdjacentContextRuns(kinds) {
|
|
1147
|
+
const runs = [];
|
|
1148
|
+
let current = null;
|
|
1149
|
+
kinds.forEach((kind, index) => {
|
|
1150
|
+
if (kind === null) {
|
|
1151
|
+
current = null;
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
if (current === null) {
|
|
1155
|
+
current = [];
|
|
1156
|
+
runs.push(current);
|
|
1157
|
+
}
|
|
1158
|
+
current.push(index);
|
|
1159
|
+
});
|
|
1160
|
+
return runs;
|
|
1161
|
+
}
|
|
1162
|
+
function getContextGroupDisplayState(contexts) {
|
|
1163
|
+
const count = contexts.length;
|
|
1164
|
+
if (count === 1) {
|
|
1165
|
+
const single = getContextDisplayState(contexts[0]);
|
|
1166
|
+
return { title: single.title, summary: single.summary, count };
|
|
1167
|
+
}
|
|
1168
|
+
const removed = contexts.filter(
|
|
1169
|
+
(context) => context.context_action === "removed"
|
|
1170
|
+
).length;
|
|
1171
|
+
const updated = count - removed;
|
|
1172
|
+
const parts = [];
|
|
1173
|
+
if (updated > 0) parts.push(`${updated} \u9879\u5DF2\u66F4\u65B0`);
|
|
1174
|
+
if (removed > 0) parts.push(`${removed} \u9879\u5DF2\u505C\u7528`);
|
|
1175
|
+
return {
|
|
1176
|
+
title: "\u5BF9\u8BDD\u80CC\u666F",
|
|
1177
|
+
summary: parts.join(" \xB7 ") || `${count} \u9879`,
|
|
1178
|
+
count
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/shared/projection/context.ts
|
|
1183
|
+
function buildContextTurn(entry, sequence) {
|
|
1184
|
+
if (entry.kind !== "context" || !isRecord(entry.source) || !isRecord(entry.data)) {
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
const display = isRecord(entry.data.display) ? entry.data.display : {};
|
|
1188
|
+
const message = isRecord(entry.message) ? entry.message : {};
|
|
1189
|
+
const context = contextProjectionData({
|
|
1190
|
+
context_kind: typeof entry.source.kind === "string" ? entry.source.kind : null,
|
|
1191
|
+
context_key: typeof entry.source.key === "string" ? entry.source.key : null,
|
|
1192
|
+
context_name: typeof entry.data.contribution_name === "string" ? entry.data.contribution_name : null,
|
|
1193
|
+
context_order: typeof entry.data.order === "number" ? entry.data.order : null,
|
|
1194
|
+
context_action: entry.data.action === "published" || entry.data.action === "retained" || entry.data.action === "removed" ? entry.data.action : null,
|
|
1195
|
+
context_revision: typeof entry.data.revision === "string" ? entry.data.revision : null,
|
|
1196
|
+
display_title: typeof display.title === "string" ? display.title : null,
|
|
1197
|
+
display_summary: typeof display.summary === "string" ? display.summary : null,
|
|
1198
|
+
sources: Array.isArray(display.sources) ? display.sources.filter(isRecord) : []
|
|
1199
|
+
});
|
|
1200
|
+
if (!context) return null;
|
|
1201
|
+
const turnId = typeof entry.id === "string" && entry.id ? entry.id : `context-${sequence}`;
|
|
1202
|
+
return {
|
|
1203
|
+
id: turnId,
|
|
1204
|
+
sequence,
|
|
1205
|
+
turn_id: turnId,
|
|
1206
|
+
loop_id: typeof entry.loop_name === "string" && entry.loop_name ? entry.loop_name : "root",
|
|
1207
|
+
kind: "context",
|
|
1208
|
+
role: "system",
|
|
1209
|
+
status: "completed",
|
|
1210
|
+
blocks: [],
|
|
1211
|
+
tool_calls: [],
|
|
1212
|
+
model: null,
|
|
1213
|
+
usage: null,
|
|
1214
|
+
duration_ms: 0,
|
|
1215
|
+
context_content: Object.hasOwn(message, "content") ? message.content : null,
|
|
1216
|
+
...context
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1026
1220
|
// src/shared/projection/builder.ts
|
|
1221
|
+
function acknowledgeTurnEvents(acknowledge) {
|
|
1222
|
+
acknowledge?.();
|
|
1223
|
+
}
|
|
1027
1224
|
function hasChatRunEvent(events) {
|
|
1028
1225
|
return events.some(
|
|
1029
|
-
(event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
1226
|
+
(event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
1030
1227
|
);
|
|
1031
1228
|
}
|
|
1032
1229
|
function acceptedPostChatFollowupCompletesLatestRun(events, updates) {
|
|
@@ -1058,7 +1255,8 @@ function completedResponseText(content) {
|
|
|
1058
1255
|
).filter((part) => part.type === "text").map((part) => String(part.text ?? "")).join("");
|
|
1059
1256
|
}
|
|
1060
1257
|
function replaceTextContent(state, content) {
|
|
1061
|
-
|
|
1258
|
+
const currentContent = state.blocks.filter((block) => block.type === "text").map((block) => String(block.content ?? "")).join("");
|
|
1259
|
+
if (currentContent === content) return;
|
|
1062
1260
|
const replacement = { type: "text", content };
|
|
1063
1261
|
const blocks = [];
|
|
1064
1262
|
let inserted = false;
|
|
@@ -1078,10 +1276,36 @@ function replaceTextContent(state, content) {
|
|
|
1078
1276
|
}
|
|
1079
1277
|
state.blocks = blocks;
|
|
1080
1278
|
}
|
|
1279
|
+
function applyResponseBlockOrder(state, value) {
|
|
1280
|
+
if (!Array.isArray(value)) return false;
|
|
1281
|
+
const ordered = [];
|
|
1282
|
+
for (const item of value) {
|
|
1283
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
|
|
1284
|
+
const block = item;
|
|
1285
|
+
if (block.type === "text" || block.type === "thinking") {
|
|
1286
|
+
const content = String(block[block.type] ?? "");
|
|
1287
|
+
if (content) ordered.push({ type: block.type, content });
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
if (block.type !== "tool_call") continue;
|
|
1291
|
+
const toolCallId = String(block.id ?? "");
|
|
1292
|
+
const toolBlock = state.blocks.find(
|
|
1293
|
+
(candidate) => candidate.type === "tool_use" && candidate.tool_call_id === toolCallId
|
|
1294
|
+
);
|
|
1295
|
+
if (toolBlock) ordered.push(toolBlock);
|
|
1296
|
+
}
|
|
1297
|
+
if (ordered.length === 0 && value.length > 0) return false;
|
|
1298
|
+
const unrelatedBlocks = state.blocks.filter(
|
|
1299
|
+
(block) => block.type !== "text" && block.type !== "thinking" && block.type !== "tool_use"
|
|
1300
|
+
);
|
|
1301
|
+
state.blocks = [...ordered, ...unrelatedBlocks];
|
|
1302
|
+
return true;
|
|
1303
|
+
}
|
|
1081
1304
|
var ClientProjectionBuilder = class {
|
|
1082
1305
|
activeTurns = /* @__PURE__ */ new Map();
|
|
1083
1306
|
lastTurnIds = /* @__PURE__ */ new Map();
|
|
1084
1307
|
latestTurns = /* @__PURE__ */ new Map();
|
|
1308
|
+
turnsById = /* @__PURE__ */ new Map();
|
|
1085
1309
|
loopDescriptions = /* @__PURE__ */ new Map();
|
|
1086
1310
|
pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1087
1311
|
pendingMemoryRefs = /* @__PURE__ */ new Map();
|
|
@@ -1117,6 +1341,8 @@ var ClientProjectionBuilder = class {
|
|
|
1117
1341
|
return this.onMemoryInject(loopId, payload);
|
|
1118
1342
|
case "memory:inject:none":
|
|
1119
1343
|
return null;
|
|
1344
|
+
case "context:entry":
|
|
1345
|
+
return this.onContextEntry(payload);
|
|
1120
1346
|
case "loop:turn":
|
|
1121
1347
|
return this.onLoopTurn(loopId, payload);
|
|
1122
1348
|
case "mode:change":
|
|
@@ -1140,6 +1366,25 @@ var ClientProjectionBuilder = class {
|
|
|
1140
1366
|
}
|
|
1141
1367
|
if (event.type.startsWith("compaction:"))
|
|
1142
1368
|
return this.onCompaction(event.type, loopId, payload);
|
|
1369
|
+
if (event.type === "llm:first_token:delivered") {
|
|
1370
|
+
const targetTurnId = String(payload.turn_id ?? "");
|
|
1371
|
+
const active = this.activeTurns.get(loopId);
|
|
1372
|
+
if (active && (!targetTurnId || active.turnId === targetTurnId)) {
|
|
1373
|
+
this.applyFirstTokenDelivery(active, payload);
|
|
1374
|
+
return this.syncPatch(active);
|
|
1375
|
+
}
|
|
1376
|
+
const latest = targetTurnId ? this.turnsById.get(targetTurnId) : this.latestTurns.get(loopId);
|
|
1377
|
+
const duration = payload.first_token_delivery_ms;
|
|
1378
|
+
if (!latest || latest.role !== "assistant" || typeof duration !== "number" || duration < 0) {
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
const turn = { ...latest, first_token_delivery_ms: duration };
|
|
1382
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1383
|
+
if (this.latestTurns.get(loopId)?.turn_id === turn.turn_id) {
|
|
1384
|
+
this.latestTurns.set(loopId, turn);
|
|
1385
|
+
}
|
|
1386
|
+
return [{ kind: "upsert", turn }];
|
|
1387
|
+
}
|
|
1143
1388
|
const state = this.activeTurns.get(loopId);
|
|
1144
1389
|
if (!state) {
|
|
1145
1390
|
if (event.type === "chat:end") {
|
|
@@ -1155,16 +1400,28 @@ var ClientProjectionBuilder = class {
|
|
|
1155
1400
|
state.toolCalls = [];
|
|
1156
1401
|
state.usage = null;
|
|
1157
1402
|
state.responseDone = false;
|
|
1403
|
+
state.firstTokenMs = null;
|
|
1404
|
+
state.streamDurationMs = null;
|
|
1405
|
+
state.firstTokenDeliveryMs = null;
|
|
1158
1406
|
return this.syncPatch(state);
|
|
1159
1407
|
case "llm:thinking:delta":
|
|
1408
|
+
this.applyFirstTokenDelivery(state, payload);
|
|
1160
1409
|
return this.onDelta(state, "thinking", "", String(payload.content ?? ""));
|
|
1161
1410
|
case "llm:content:delta":
|
|
1411
|
+
this.applyFirstTokenDelivery(state, payload);
|
|
1162
1412
|
return this.onDelta(state, "text", "", String(payload.content ?? ""));
|
|
1163
1413
|
case "llm:content:complete":
|
|
1164
1414
|
replaceTextContent(state, String(payload.content ?? ""));
|
|
1165
1415
|
return this.syncPatch(state);
|
|
1416
|
+
case "llm:content:done":
|
|
1417
|
+
this.applyStreamTiming(state, payload);
|
|
1418
|
+
return this.syncPatch(state);
|
|
1166
1419
|
case "llm:tool_call:created":
|
|
1420
|
+
this.applyFirstTokenDelivery(state, payload);
|
|
1167
1421
|
return this.onToolCallCreated(state, payload);
|
|
1422
|
+
case "llm:tool_call:arguments:delta":
|
|
1423
|
+
this.applyFirstTokenDelivery(state, payload);
|
|
1424
|
+
return this.onToolCallArgumentsDelta(state, payload);
|
|
1168
1425
|
case "llm:response:done":
|
|
1169
1426
|
return this.onResponseDone(state, loopId, payload);
|
|
1170
1427
|
case "tool:result:delta":
|
|
@@ -1191,6 +1448,7 @@ var ClientProjectionBuilder = class {
|
|
|
1191
1448
|
this.activeTurns.clear();
|
|
1192
1449
|
this.lastTurnIds.clear();
|
|
1193
1450
|
this.latestTurns.clear();
|
|
1451
|
+
this.turnsById.clear();
|
|
1194
1452
|
this.loopDescriptions.clear();
|
|
1195
1453
|
this.pendingChildPauses.clear();
|
|
1196
1454
|
this.pendingMemoryRefs.clear();
|
|
@@ -1206,15 +1464,18 @@ var ClientProjectionBuilder = class {
|
|
|
1206
1464
|
}
|
|
1207
1465
|
_seedTurns(turns) {
|
|
1208
1466
|
const latestTurns = /* @__PURE__ */ new Map();
|
|
1467
|
+
const turnsById = /* @__PURE__ */ new Map();
|
|
1209
1468
|
for (const turn of turns) {
|
|
1210
1469
|
this.seedSequence(turn.sequence);
|
|
1211
1470
|
if (!isConversationTurn(turn)) continue;
|
|
1471
|
+
turnsById.set(turn.turn_id, turn);
|
|
1212
1472
|
const current = latestTurns.get(turn.loop_id);
|
|
1213
1473
|
if (!current || turn.sequence >= current.sequence) {
|
|
1214
1474
|
latestTurns.set(turn.loop_id, turn);
|
|
1215
1475
|
}
|
|
1216
1476
|
}
|
|
1217
1477
|
this.latestTurns = latestTurns;
|
|
1478
|
+
this.turnsById = turnsById;
|
|
1218
1479
|
}
|
|
1219
1480
|
// --- Event handlers ---
|
|
1220
1481
|
onMemoryInject(loopId, payload) {
|
|
@@ -1232,6 +1493,11 @@ var ClientProjectionBuilder = class {
|
|
|
1232
1493
|
}
|
|
1233
1494
|
return null;
|
|
1234
1495
|
}
|
|
1496
|
+
onContextEntry(payload) {
|
|
1497
|
+
if (!isRecord(payload.entry)) return null;
|
|
1498
|
+
const turn = buildContextTurn(payload.entry, this.nextSeq());
|
|
1499
|
+
return turn ? [{ kind: "upsert", turn }] : null;
|
|
1500
|
+
}
|
|
1235
1501
|
onLoopTurn(loopId, payload) {
|
|
1236
1502
|
let turnId = String(payload.turn_id ?? this.nextTurnId(`turn:${loopId}`));
|
|
1237
1503
|
let memoryRefs = null;
|
|
@@ -1252,6 +1518,7 @@ var ClientProjectionBuilder = class {
|
|
|
1252
1518
|
this.lastTurnIds.set(loopId, turnId);
|
|
1253
1519
|
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1254
1520
|
this.latestTurns.set(loopId, turn);
|
|
1521
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1255
1522
|
return [{ kind: "upsert", turn }];
|
|
1256
1523
|
}
|
|
1257
1524
|
onModeChange(loopId, payload) {
|
|
@@ -1288,11 +1555,11 @@ var ClientProjectionBuilder = class {
|
|
|
1288
1555
|
loopId,
|
|
1289
1556
|
toolCallId: optStr(payload.tool_call_id) ?? void 0,
|
|
1290
1557
|
toolName: toolNameFromPayload(payload) ?? void 0,
|
|
1291
|
-
workspacePath:
|
|
1558
|
+
workspacePath: isRecord(payload.workspace_change) ? optStr(payload.workspace_change.to) ?? void 0 : void 0
|
|
1292
1559
|
}
|
|
1293
1560
|
];
|
|
1294
1561
|
const change = payload.workspace_change;
|
|
1295
|
-
if (
|
|
1562
|
+
if (isRecord(change)) {
|
|
1296
1563
|
const source = optStr(change.source) ?? "";
|
|
1297
1564
|
const projectName = optStr(change.project_name) ?? "";
|
|
1298
1565
|
const fromPath = optStr(change.from) ?? "";
|
|
@@ -1385,7 +1652,32 @@ var ClientProjectionBuilder = class {
|
|
|
1385
1652
|
}
|
|
1386
1653
|
const directTurn = this.activeTurns.get(loopId) ?? this.latestTurns.get(loopId);
|
|
1387
1654
|
setToolCallStatus(directTurn, toolCallId, "done", null, null);
|
|
1388
|
-
|
|
1655
|
+
const updates = this.collectLoopUpdates(loopId, parentLoopId);
|
|
1656
|
+
const entryId = typeof payload.entry_id === "string" ? payload.entry_id : "";
|
|
1657
|
+
if (!entryId) return updates;
|
|
1658
|
+
const answerData = isRecord(payload.answer_data) ? payload.answer_data : {};
|
|
1659
|
+
const answerTurn = {
|
|
1660
|
+
id: entryId,
|
|
1661
|
+
sequence: this.nextSeq(),
|
|
1662
|
+
turn_id: entryId,
|
|
1663
|
+
loop_id: loopId,
|
|
1664
|
+
kind: "message",
|
|
1665
|
+
role: "assistant",
|
|
1666
|
+
status: "completed",
|
|
1667
|
+
blocks: [
|
|
1668
|
+
{
|
|
1669
|
+
type: "ask_user_answer",
|
|
1670
|
+
content: answerData,
|
|
1671
|
+
tool_call_id: toolCallId || null
|
|
1672
|
+
}
|
|
1673
|
+
],
|
|
1674
|
+
tool_calls: [],
|
|
1675
|
+
model: null,
|
|
1676
|
+
usage: null,
|
|
1677
|
+
duration_ms: 0,
|
|
1678
|
+
client_request_id: typeof payload.client_request_id === "string" ? payload.client_request_id : null
|
|
1679
|
+
};
|
|
1680
|
+
return [...updates, { kind: "upsert", turn: answerTurn }];
|
|
1389
1681
|
}
|
|
1390
1682
|
onUserMessage(_loopId, payload) {
|
|
1391
1683
|
const entryId = String(payload.entry_id ?? this.nextTurnId("user"));
|
|
@@ -1402,7 +1694,8 @@ var ClientProjectionBuilder = class {
|
|
|
1402
1694
|
tool_calls: [],
|
|
1403
1695
|
model: null,
|
|
1404
1696
|
usage: null,
|
|
1405
|
-
duration_ms: 0
|
|
1697
|
+
duration_ms: 0,
|
|
1698
|
+
client_request_id: typeof payload.client_request_id === "string" ? payload.client_request_id : null
|
|
1406
1699
|
};
|
|
1407
1700
|
this.latestTurns.set("root", turn);
|
|
1408
1701
|
return [{ kind: "upsert", turn }];
|
|
@@ -1430,13 +1723,7 @@ var ClientProjectionBuilder = class {
|
|
|
1430
1723
|
}
|
|
1431
1724
|
onToolCallCreated(state, payload) {
|
|
1432
1725
|
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;
|
|
1726
|
+
return this.onToolCallArgumentsDelta(state, payload);
|
|
1440
1727
|
}
|
|
1441
1728
|
const toolCallId = String(payload.id ?? "");
|
|
1442
1729
|
const toolName = toolNameFromPayload(payload);
|
|
@@ -1445,16 +1732,21 @@ var ClientProjectionBuilder = class {
|
|
|
1445
1732
|
upsertToolCall(state, toolCallId, toolName, displayName, args);
|
|
1446
1733
|
return this.syncPatch(state);
|
|
1447
1734
|
}
|
|
1735
|
+
onToolCallArgumentsDelta(state, payload) {
|
|
1736
|
+
const delta = String(payload.arguments_delta ?? "");
|
|
1737
|
+
const toolCallId = String(payload.id ?? "");
|
|
1738
|
+
if (!delta || !toolCallId) return null;
|
|
1739
|
+
appendToolCallArguments(state, toolCallId, delta);
|
|
1740
|
+
return this.syncPatch(state);
|
|
1741
|
+
}
|
|
1448
1742
|
onResponseDone(state, loopId, payload) {
|
|
1449
1743
|
state.responseDone = true;
|
|
1744
|
+
this.applyStreamTiming(state, payload);
|
|
1450
1745
|
const model = optStr(payload.model);
|
|
1451
1746
|
if (model) state.model = model;
|
|
1452
1747
|
if (typeof payload.usage === "object" && payload.usage !== null) {
|
|
1453
1748
|
state.usage = { ...payload.usage };
|
|
1454
1749
|
}
|
|
1455
|
-
if ("content" in payload) {
|
|
1456
|
-
replaceTextContent(state, completedResponseText(payload.content));
|
|
1457
|
-
}
|
|
1458
1750
|
const toolCalls = payload.tool_calls;
|
|
1459
1751
|
if (Array.isArray(toolCalls)) {
|
|
1460
1752
|
for (const raw of toolCalls) {
|
|
@@ -1472,11 +1764,35 @@ var ClientProjectionBuilder = class {
|
|
|
1472
1764
|
);
|
|
1473
1765
|
}
|
|
1474
1766
|
}
|
|
1475
|
-
if (state.
|
|
1767
|
+
if (!applyResponseBlockOrder(state, payload.response_blocks) && "content" in payload) {
|
|
1768
|
+
replaceTextContent(state, completedResponseText(payload.content));
|
|
1769
|
+
}
|
|
1770
|
+
const isNativeProviderResponse = typeof payload.provider === "string" && payload.provider !== "";
|
|
1771
|
+
if (state.toolCalls.length === 0 || isNativeProviderResponse && state.toolCalls.every(
|
|
1772
|
+
(toolCall) => toolCall.status !== "pending" && toolCall.status !== "awaiting_answer"
|
|
1773
|
+
)) {
|
|
1476
1774
|
return this.finalize(loopId, "completed");
|
|
1477
1775
|
}
|
|
1478
1776
|
return this.syncPatch(state);
|
|
1479
1777
|
}
|
|
1778
|
+
applyFirstTokenDelivery(state, payload) {
|
|
1779
|
+
const firstTokenMs = payload.first_token_ms;
|
|
1780
|
+
if (state.firstTokenMs === null && typeof firstTokenMs === "number" && firstTokenMs >= 0) {
|
|
1781
|
+
state.firstTokenMs = Math.round(firstTokenMs);
|
|
1782
|
+
}
|
|
1783
|
+
const duration = payload.first_token_delivery_ms;
|
|
1784
|
+
if (state.firstTokenDeliveryMs === null && typeof duration === "number" && duration >= 0) {
|
|
1785
|
+
state.firstTokenDeliveryMs = duration;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
applyStreamTiming(state, payload) {
|
|
1789
|
+
if (typeof payload.first_token_ms === "number" && payload.first_token_ms >= 0) {
|
|
1790
|
+
state.firstTokenMs = Math.round(payload.first_token_ms);
|
|
1791
|
+
}
|
|
1792
|
+
if (typeof payload.stream_duration_ms === "number" && payload.stream_duration_ms >= 0) {
|
|
1793
|
+
state.streamDurationMs = Math.round(payload.stream_duration_ms);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1480
1796
|
onToolResultDone(state, loopId, payload) {
|
|
1481
1797
|
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1482
1798
|
const streamedResult = state.toolCalls.find((toolCall) => toolCall.id === toolCallId)?.result;
|
|
@@ -1484,9 +1800,11 @@ var ClientProjectionBuilder = class {
|
|
|
1484
1800
|
const status = toolStatusFromResult(content);
|
|
1485
1801
|
const rawDuration = payload.duration_ms;
|
|
1486
1802
|
const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
|
|
1803
|
+
const ranInParallel = typeof payload.ran_in_parallel === "boolean" ? payload.ran_in_parallel : null;
|
|
1487
1804
|
const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
|
|
1488
|
-
applyToolResult(state, toolCallId, content, status, durationMs, sl);
|
|
1489
|
-
|
|
1805
|
+
applyToolResult(state, toolCallId, content, status, durationMs, ranInParallel, sl);
|
|
1806
|
+
const isNativeProviderResult = typeof payload.provider === "string" && payload.provider !== "";
|
|
1807
|
+
if (!isNativeProviderResult && this.shouldFinalizeAfterToolResults(state)) {
|
|
1490
1808
|
return this.finalize(loopId, "completed");
|
|
1491
1809
|
}
|
|
1492
1810
|
return this.syncPatch(state);
|
|
@@ -1515,10 +1833,13 @@ var ClientProjectionBuilder = class {
|
|
|
1515
1833
|
onToolBridge(state, payload) {
|
|
1516
1834
|
const bridge = payload.bridge;
|
|
1517
1835
|
if (typeof bridge !== "object" || bridge === null) return null;
|
|
1836
|
+
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1837
|
+
if (!toolCallId || !state.toolCalls.some((toolCall) => toolCall.id === toolCallId)) return null;
|
|
1518
1838
|
state.blocks.push({
|
|
1519
1839
|
type: "tool_bridge",
|
|
1520
1840
|
content: bridge,
|
|
1521
|
-
|
|
1841
|
+
delivery_id: String(payload.delivery_id ?? "") || null,
|
|
1842
|
+
tool_call_id: toolCallId
|
|
1522
1843
|
});
|
|
1523
1844
|
return this.syncPatch(state);
|
|
1524
1845
|
}
|
|
@@ -1527,6 +1848,11 @@ var ClientProjectionBuilder = class {
|
|
|
1527
1848
|
const toolName = String(payload.tool_name ?? "");
|
|
1528
1849
|
const displayName = this.displayNameResolver(toolName);
|
|
1529
1850
|
upsertToolCall(state, toolCallId, toolName, displayName, "");
|
|
1851
|
+
const relatedToolCallId = String(payload.related_tool_call_id ?? "");
|
|
1852
|
+
if (relatedToolCallId && relatedToolCallId !== toolCallId) {
|
|
1853
|
+
const related = state.toolCalls.find((toolCall) => toolCall.id === relatedToolCallId);
|
|
1854
|
+
if (related) related.status = "awaiting_answer";
|
|
1855
|
+
}
|
|
1530
1856
|
return this.syncPatch(state);
|
|
1531
1857
|
}
|
|
1532
1858
|
onChildPause(state, loopId, payload) {
|
|
@@ -1707,6 +2033,7 @@ var ClientProjectionBuilder = class {
|
|
|
1707
2033
|
syncPatch(state) {
|
|
1708
2034
|
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
1709
2035
|
this.latestTurns.set(state.loopId, turn);
|
|
2036
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1710
2037
|
this.lastTurnIds.set(state.loopId, turn.turn_id);
|
|
1711
2038
|
return [{ kind: "upsert", turn }];
|
|
1712
2039
|
}
|
|
@@ -1717,6 +2044,7 @@ var ClientProjectionBuilder = class {
|
|
|
1717
2044
|
const turn = snapshot(state, this.nextSeq(), status);
|
|
1718
2045
|
this.lastTurnIds.set(loopId, turn.turn_id);
|
|
1719
2046
|
this.latestTurns.set(loopId, turn);
|
|
2047
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1720
2048
|
return [{ kind: "upsert", turn }];
|
|
1721
2049
|
}
|
|
1722
2050
|
shouldFinalizeAfterToolResults(state) {
|
|
@@ -1754,9 +2082,6 @@ var ClientProjectionBuilder = class {
|
|
|
1754
2082
|
return `${prefix}:${this.syntheticCounter}:${hex}`;
|
|
1755
2083
|
}
|
|
1756
2084
|
};
|
|
1757
|
-
function isRecord2(value) {
|
|
1758
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1759
|
-
}
|
|
1760
2085
|
function optStr(value) {
|
|
1761
2086
|
if (value == null || value === "") return null;
|
|
1762
2087
|
return String(value);
|
|
@@ -1783,6 +2108,8 @@ function projectHistory(entries) {
|
|
|
1783
2108
|
const latestAssistantByLoop = /* @__PURE__ */ new Map();
|
|
1784
2109
|
const pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1785
2110
|
const answeredToolCallIds = /* @__PURE__ */ new Set();
|
|
2111
|
+
const nativeToolTurns = /* @__PURE__ */ new Set();
|
|
2112
|
+
const turnEntryAliases = /* @__PURE__ */ new Map();
|
|
1786
2113
|
let latestRootMessage = null;
|
|
1787
2114
|
let latestRootAssistant = null;
|
|
1788
2115
|
let sequence = 0;
|
|
@@ -1790,7 +2117,14 @@ function projectHistory(entries) {
|
|
|
1790
2117
|
const kind = String(entry.kind ?? "");
|
|
1791
2118
|
const loopId = String(entry.loop_name ?? "root");
|
|
1792
2119
|
const id = String(entry.id ?? `history-${sequence + 1}`);
|
|
1793
|
-
if (kind === "
|
|
2120
|
+
if (kind === "turn_timing" && isRecord(entry.data)) {
|
|
2121
|
+
const turnId = typeof entry.data.turn_id === "string" ? entry.data.turn_id : "";
|
|
2122
|
+
const duration = numberOrNull(entry.data.first_token_delivery_ms);
|
|
2123
|
+
const target = turnEntryAliases.get(turnId);
|
|
2124
|
+
if (target && duration !== null) target.first_token_delivery_ms = duration;
|
|
2125
|
+
continue;
|
|
2126
|
+
}
|
|
2127
|
+
if (kind === "post_chat_followup" && isRecord(entry.data)) {
|
|
1794
2128
|
const assistantEntryId = String(entry.data.assistant_entry_id ?? "");
|
|
1795
2129
|
if (latestRootMessage?.role === "assistant" && latestRootMessage.turn_id === assistantEntryId) {
|
|
1796
2130
|
const followup = normalizePostChatFollowup(entry.data, assistantEntryId);
|
|
@@ -1798,7 +2132,15 @@ function projectHistory(entries) {
|
|
|
1798
2132
|
}
|
|
1799
2133
|
continue;
|
|
1800
2134
|
}
|
|
1801
|
-
if (kind === "
|
|
2135
|
+
if (kind === "context") {
|
|
2136
|
+
const turn = buildContextTurn(entry, sequence + 1);
|
|
2137
|
+
if (turn) {
|
|
2138
|
+
sequence += 1;
|
|
2139
|
+
turns.push(turn);
|
|
2140
|
+
}
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
if (kind === "message" && isRecord(entry.message)) {
|
|
1802
2144
|
const message = entry.message;
|
|
1803
2145
|
const role = String(message.role ?? "");
|
|
1804
2146
|
if (role === "tool") {
|
|
@@ -1808,6 +2150,9 @@ function projectHistory(entries) {
|
|
|
1808
2150
|
if (owner && toolCall) {
|
|
1809
2151
|
toolCall.status = message._error ? "error" : inferToolStatus(String(message.content ?? ""));
|
|
1810
2152
|
toolCall.result = message.content ?? "";
|
|
2153
|
+
const duration = numberOrNull(message._duration_ms);
|
|
2154
|
+
if (duration !== null) toolCall.duration_ms = duration;
|
|
2155
|
+
if (message._ran_in_parallel === true) toolCall.ran_in_parallel = true;
|
|
1811
2156
|
owner.blocks.push({
|
|
1812
2157
|
type: "tool_result",
|
|
1813
2158
|
content: message.content ?? "",
|
|
@@ -1817,7 +2162,7 @@ function projectHistory(entries) {
|
|
|
1817
2162
|
continue;
|
|
1818
2163
|
}
|
|
1819
2164
|
if (role !== "user" && role !== "assistant" && role !== "system") continue;
|
|
1820
|
-
const toolCalls = role === "assistant" ? buildToolCalls(message.tool_calls) : [];
|
|
2165
|
+
const toolCalls = role === "assistant" && !message._error ? buildToolCalls(message.tool_calls) : [];
|
|
1821
2166
|
const blocks = buildBlocks(message, toolCalls);
|
|
1822
2167
|
const turn = {
|
|
1823
2168
|
id,
|
|
@@ -1830,14 +2175,45 @@ function projectHistory(entries) {
|
|
|
1830
2175
|
blocks,
|
|
1831
2176
|
tool_calls: toolCalls,
|
|
1832
2177
|
model: stringOrNull(message.model),
|
|
1833
|
-
usage:
|
|
2178
|
+
usage: isRecord(message._usage) ? { ...message._usage } : null,
|
|
1834
2179
|
duration_ms: numberOrZero(message._duration_ms),
|
|
1835
|
-
|
|
2180
|
+
first_token_ms: numberOrNull(message._first_token_ms),
|
|
2181
|
+
stream_duration_ms: numberOrNull(message._stream_duration_ms),
|
|
2182
|
+
started_at: typeof entry.timestamp === "string" ? entry.timestamp : void 0,
|
|
2183
|
+
// fork 子会话的父锚点:历史里落在消息级 _parent_fork_tool_call_id。丢掉它,
|
|
2184
|
+
// 刷新后工作流步骤 fork 就配不回 workflow:run 工具调用(普通 Agent fork 有
|
|
2185
|
+
// root Agent 工具调用兜底配对,工作流 fork 没有,子智能体卡会整体消失)。
|
|
2186
|
+
parent_fork_tool_call_id: stringOrNull(message._parent_fork_tool_call_id)
|
|
1836
2187
|
};
|
|
2188
|
+
const isNativeToolEntry = role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.some(
|
|
2189
|
+
(toolCall) => isRecord(toolCall) && typeof toolCall.provider === "string"
|
|
2190
|
+
);
|
|
2191
|
+
const previousTurn = turns[turns.length - 1];
|
|
2192
|
+
if (role === "assistant" && previousTurn?.role === "assistant" && previousTurn.loop_id === loopId && nativeToolTurns.has(previousTurn)) {
|
|
2193
|
+
nativeToolTurns.delete(previousTurn);
|
|
2194
|
+
previousTurn.blocks.push(...turn.blocks);
|
|
2195
|
+
previousTurn.tool_calls.push(...turn.tool_calls);
|
|
2196
|
+
previousTurn.status = turn.status;
|
|
2197
|
+
previousTurn.model = turn.model ?? previousTurn.model;
|
|
2198
|
+
previousTurn.usage = turn.usage ?? previousTurn.usage;
|
|
2199
|
+
previousTurn.duration_ms = turn.duration_ms || previousTurn.duration_ms;
|
|
2200
|
+
previousTurn.first_token_ms = turn.first_token_ms ?? previousTurn.first_token_ms;
|
|
2201
|
+
previousTurn.stream_duration_ms = turn.stream_duration_ms ?? previousTurn.stream_duration_ms;
|
|
2202
|
+
if (turn.started_at) previousTurn.started_at = turn.started_at;
|
|
2203
|
+
previousTurn.id = turn.id;
|
|
2204
|
+
previousTurn.turn_id = turn.turn_id;
|
|
2205
|
+
if (isNativeToolEntry) nativeToolTurns.add(previousTurn);
|
|
2206
|
+
turnEntryAliases.set(id, previousTurn);
|
|
2207
|
+
if (loopId === "root") latestRootMessage = previousTurn;
|
|
2208
|
+
latestAssistantByLoop.set(loopId, previousTurn);
|
|
2209
|
+
continue;
|
|
2210
|
+
}
|
|
1837
2211
|
if (loopId === "root" && (role === "user" || role === "assistant")) {
|
|
1838
2212
|
latestRootMessage = turn;
|
|
1839
2213
|
}
|
|
1840
2214
|
turns.push(turn);
|
|
2215
|
+
turnEntryAliases.set(id, turn);
|
|
2216
|
+
if (isNativeToolEntry) nativeToolTurns.add(turn);
|
|
1841
2217
|
if (role === "assistant") latestAssistantByLoop.set(loopId, turn);
|
|
1842
2218
|
if (loopId === "root") {
|
|
1843
2219
|
if (role === "assistant") latestRootAssistant = turn;
|
|
@@ -1846,10 +2222,25 @@ function projectHistory(entries) {
|
|
|
1846
2222
|
for (const toolCall of toolCalls) toolOwners.set(toolCall.id, turn);
|
|
1847
2223
|
continue;
|
|
1848
2224
|
}
|
|
2225
|
+
if (kind === "tool_result") {
|
|
2226
|
+
const data = entry.data ?? {};
|
|
2227
|
+
const toolCallId = String(data.tool_call_id ?? "");
|
|
2228
|
+
const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
2229
|
+
if (owner && toolCallId) {
|
|
2230
|
+
const rawContent = data.content;
|
|
2231
|
+
const content = typeof rawContent === "string" ? rawContent : Array.isArray(rawContent) ? rawContent : String(rawContent ?? "");
|
|
2232
|
+
const status = data.is_error === true ? "error" : inferToolStatus(typeof content === "string" ? content : JSON.stringify(content));
|
|
2233
|
+
setToolCallStatus(owner, toolCallId, status, content, null);
|
|
2234
|
+
if (!owner.blocks.some((block) => block.type === "tool_result" && block.tool_call_id === toolCallId)) {
|
|
2235
|
+
owner.blocks.push({ type: "tool_result", content, tool_call_id: toolCallId });
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
1849
2240
|
if (kind === "mode_change") {
|
|
1850
2241
|
turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
|
|
1851
2242
|
} else if (kind === "workspace_change") {
|
|
1852
|
-
const change =
|
|
2243
|
+
const change = isRecord(entry.data) ? entry.data : {};
|
|
1853
2244
|
const source = stringOrNull(change.source) ?? "";
|
|
1854
2245
|
const projectName = stringOrNull(change.project_name) ?? "";
|
|
1855
2246
|
const fromPath = stringOrNull(change.from) ?? "";
|
|
@@ -1878,19 +2269,20 @@ function projectHistory(entries) {
|
|
|
1878
2269
|
} else if (kind === "tool_ui" || kind === "tool_bridge") {
|
|
1879
2270
|
const data = entry.data ?? {};
|
|
1880
2271
|
const toolCallId = String(data.tool_call_id ?? "");
|
|
1881
|
-
const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
2272
|
+
const owner = kind === "tool_bridge" ? toolCallId ? toolOwners.get(toolCallId) : void 0 : toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
1882
2273
|
const content = kind === "tool_ui" ? data.ui : data.bridge;
|
|
1883
2274
|
if (owner && content !== void 0) {
|
|
1884
2275
|
owner.blocks.push({
|
|
1885
2276
|
type: kind,
|
|
1886
2277
|
content,
|
|
2278
|
+
...kind === "tool_bridge" ? { delivery_id: id } : {},
|
|
1887
2279
|
tool_call_id: toolCallId || null
|
|
1888
2280
|
});
|
|
1889
2281
|
}
|
|
1890
2282
|
} else if (kind === "compaction" || kind === "tool_result_archive") {
|
|
1891
2283
|
const data = entry.data ?? {};
|
|
1892
2284
|
const compactionId = String(data.compaction_id ?? entry.id ?? `history-${sequence + 1}`);
|
|
1893
|
-
let content =
|
|
2285
|
+
let content = isRecord(entry.message) ? entry.message.content : void 0;
|
|
1894
2286
|
if (kind === "tool_result_archive") {
|
|
1895
2287
|
const archivedCount = Array.isArray(data.archived_files) ? data.archived_files.length : 0;
|
|
1896
2288
|
content = `<compaction-summary>
|
|
@@ -1898,18 +2290,24 @@ function projectHistory(entries) {
|
|
|
1898
2290
|
</compaction-summary>`;
|
|
1899
2291
|
}
|
|
1900
2292
|
turns.push(
|
|
1901
|
-
buildCompactionTurn(
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2293
|
+
buildCompactionTurn(
|
|
2294
|
+
++sequence,
|
|
2295
|
+
compactionId,
|
|
2296
|
+
loopId,
|
|
2297
|
+
data.status === "failed" ? "failed" : "completed",
|
|
2298
|
+
{
|
|
2299
|
+
...data,
|
|
2300
|
+
content
|
|
2301
|
+
}
|
|
2302
|
+
)
|
|
1905
2303
|
);
|
|
1906
2304
|
} else if (kind === "child_pause") {
|
|
1907
2305
|
const data = entry.data ?? {};
|
|
1908
|
-
const sourceLoop =
|
|
2306
|
+
const sourceLoop = isRecord(data.source_loop) ? data.source_loop : {};
|
|
1909
2307
|
const childLoopId = String(data.child_loop_name ?? sourceLoop.name ?? "");
|
|
1910
2308
|
const childToolCallId = String(data.child_pause_tool_call_id ?? "");
|
|
1911
2309
|
const parentToolCallId = String(data.parent_fork_tool_call_id ?? "");
|
|
1912
|
-
const pauseToolData =
|
|
2310
|
+
const pauseToolData = isRecord(data.pause_tool_data) ? data.pause_tool_data : {};
|
|
1913
2311
|
const description = String(sourceLoop.description ?? "");
|
|
1914
2312
|
applyAskUserPauseToTurn(
|
|
1915
2313
|
latestAssistantByLoop.get(childLoopId),
|
|
@@ -1997,15 +2395,15 @@ function parseToolArguments(value) {
|
|
|
1997
2395
|
if (!value) return {};
|
|
1998
2396
|
try {
|
|
1999
2397
|
const parsed = JSON.parse(value);
|
|
2000
|
-
return
|
|
2398
|
+
return isRecord(parsed) ? parsed : {};
|
|
2001
2399
|
} catch {
|
|
2002
2400
|
return {};
|
|
2003
2401
|
}
|
|
2004
2402
|
}
|
|
2005
2403
|
function buildToolCalls(value) {
|
|
2006
2404
|
if (!Array.isArray(value)) return [];
|
|
2007
|
-
return value.filter(
|
|
2008
|
-
const fn =
|
|
2405
|
+
return value.filter(isRecord).map((raw) => {
|
|
2406
|
+
const fn = isRecord(raw.function) ? raw.function : {};
|
|
2009
2407
|
const name = String(fn.name ?? "");
|
|
2010
2408
|
return {
|
|
2011
2409
|
id: String(raw.id ?? ""),
|
|
@@ -2021,23 +2419,40 @@ function buildBlocks(message, toolCalls) {
|
|
|
2021
2419
|
const displayContent = message.role === "user" ? message._display_content : void 0;
|
|
2022
2420
|
const stored = message._blocks;
|
|
2023
2421
|
const blocks = [];
|
|
2422
|
+
const storedToolCallIds = /* @__PURE__ */ new Set();
|
|
2024
2423
|
if (displayContent !== void 0) {
|
|
2025
2424
|
blocks.push({ type: "text", content: displayContent });
|
|
2026
2425
|
} else if (Array.isArray(stored)) {
|
|
2027
2426
|
for (const raw of stored) {
|
|
2028
|
-
if (!
|
|
2427
|
+
if (!isRecord(raw)) continue;
|
|
2029
2428
|
const type3 = String(raw.type ?? "");
|
|
2030
2429
|
if (type3 === "thinking") blocks.push({ type: type3, content: raw.thinking ?? raw.content ?? "" });
|
|
2031
2430
|
if (type3 === "text") blocks.push({ type: type3, content: raw.text ?? raw.content ?? "" });
|
|
2431
|
+
if (type3 === "tool_call") {
|
|
2432
|
+
const toolCallId = String(raw.id ?? "");
|
|
2433
|
+
const toolCall = toolCalls.find((candidate) => candidate.id === toolCallId);
|
|
2434
|
+
if (!toolCall) continue;
|
|
2435
|
+
storedToolCallIds.add(toolCallId);
|
|
2436
|
+
blocks.push({
|
|
2437
|
+
type: "tool_use",
|
|
2438
|
+
content: toolCall.arguments,
|
|
2439
|
+
tool_call_id: toolCall.id,
|
|
2440
|
+
tool_name: toolCall.tool_name,
|
|
2441
|
+
display_name: toolCall.display_name
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2032
2444
|
}
|
|
2033
2445
|
} else if (message.content !== void 0 && message.content !== "") {
|
|
2034
2446
|
blocks.push({ type: "text", content: message.content });
|
|
2035
2447
|
}
|
|
2036
2448
|
for (const toolCall of toolCalls) {
|
|
2449
|
+
if (storedToolCallIds.has(toolCall.id)) continue;
|
|
2037
2450
|
blocks.push({
|
|
2038
2451
|
type: "tool_use",
|
|
2039
|
-
content:
|
|
2040
|
-
tool_call_id: toolCall.id
|
|
2452
|
+
content: toolCall.arguments,
|
|
2453
|
+
tool_call_id: toolCall.id,
|
|
2454
|
+
tool_name: toolCall.tool_name,
|
|
2455
|
+
display_name: toolCall.display_name
|
|
2041
2456
|
});
|
|
2042
2457
|
}
|
|
2043
2458
|
return blocks;
|
|
@@ -2058,15 +2473,15 @@ function markerTurn(id, sequence, loopId, type3, content, toolCallId = null) {
|
|
|
2058
2473
|
duration_ms: 0
|
|
2059
2474
|
};
|
|
2060
2475
|
}
|
|
2061
|
-
function isRecord3(value) {
|
|
2062
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2063
|
-
}
|
|
2064
2476
|
function stringOrNull(value) {
|
|
2065
2477
|
return typeof value === "string" ? value : null;
|
|
2066
2478
|
}
|
|
2067
2479
|
function numberOrZero(value) {
|
|
2068
2480
|
return typeof value === "number" ? value : 0;
|
|
2069
2481
|
}
|
|
2482
|
+
function numberOrNull(value) {
|
|
2483
|
+
return typeof value === "number" && value >= 0 ? value : null;
|
|
2484
|
+
}
|
|
2070
2485
|
function findLatestAssistant(turns, loopId) {
|
|
2071
2486
|
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
2072
2487
|
const turn = turns[index];
|
|
@@ -2094,13 +2509,13 @@ function toCreateSessionPayload(request) {
|
|
|
2094
2509
|
solution_snapshot_session_id: request.solution_snapshot_session_id ?? null,
|
|
2095
2510
|
compaction_ratio: request.compaction_ratio ?? null,
|
|
2096
2511
|
...request.runtime_type ? { runtime_type: request.runtime_type } : {},
|
|
2097
|
-
...request.daemon_id ? { daemon_id: request.daemon_id } : {}
|
|
2512
|
+
...request.daemon_id ? { daemon_id: request.daemon_id } : {},
|
|
2513
|
+
...request.agent_runtime_id ? { agent_runtime_id: request.agent_runtime_id } : {}
|
|
2098
2514
|
};
|
|
2099
2515
|
}
|
|
2100
2516
|
var builtinSolutionIds = /* @__PURE__ */ new Set([
|
|
2101
2517
|
"app-dev",
|
|
2102
2518
|
"general_chat",
|
|
2103
|
-
"multica",
|
|
2104
2519
|
"night_build",
|
|
2105
2520
|
"skill_editor",
|
|
2106
2521
|
"smart_bid",
|
|
@@ -2329,6 +2744,27 @@ var SessionsResource = class {
|
|
|
2329
2744
|
);
|
|
2330
2745
|
return projectHistory(entries);
|
|
2331
2746
|
}
|
|
2747
|
+
async getSessionTurnsPage(sessionId, options = {}) {
|
|
2748
|
+
const query = new URLSearchParams();
|
|
2749
|
+
if (options.limit !== void 0) query.set("limit", String(options.limit));
|
|
2750
|
+
if (options.before !== void 0) query.set("before", options.before);
|
|
2751
|
+
const suffix = query.size ? `?${query}` : "";
|
|
2752
|
+
const page = await this.client.json("GET", `/api/sessions/${encodeURIComponent(sessionId)}/turns${suffix}`);
|
|
2753
|
+
return {
|
|
2754
|
+
turns: page.turns,
|
|
2755
|
+
nextBefore: page.next_before,
|
|
2756
|
+
currentMode: page.current_mode
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
async getSessionTurnCommands(sessionId) {
|
|
2760
|
+
const commands = await this.client.json("GET", `/api/sessions/${encodeURIComponent(sessionId)}/turn-commands`);
|
|
2761
|
+
return commands.map((command) => ({
|
|
2762
|
+
deliveryId: command.delivery_id,
|
|
2763
|
+
toolCallId: command.tool_call_id ?? void 0,
|
|
2764
|
+
action: command.action,
|
|
2765
|
+
payload: command.payload
|
|
2766
|
+
}));
|
|
2767
|
+
}
|
|
2332
2768
|
listResultFeedback(sessionId) {
|
|
2333
2769
|
return this.client.json("GET", `/api/sessions/${encodeURIComponent(sessionId)}/feedback`);
|
|
2334
2770
|
}
|
|
@@ -2700,6 +3136,36 @@ var Emitter = class {
|
|
|
2700
3136
|
}
|
|
2701
3137
|
};
|
|
2702
3138
|
|
|
3139
|
+
// src/session/history-page.ts
|
|
3140
|
+
function sortedUnique(turns) {
|
|
3141
|
+
const byId = /* @__PURE__ */ new Map();
|
|
3142
|
+
for (const turn of turns) byId.set(turn.turn_id, turn);
|
|
3143
|
+
return [...byId.values()].sort((left, right) => left.sequence - right.sequence);
|
|
3144
|
+
}
|
|
3145
|
+
function prependOlder(current, page) {
|
|
3146
|
+
const currentById = new Map(current.map((turn) => [turn.turn_id, turn]));
|
|
3147
|
+
return sortedUnique([
|
|
3148
|
+
...current,
|
|
3149
|
+
...page.map((turn) => {
|
|
3150
|
+
const live = currentById.get(turn.turn_id);
|
|
3151
|
+
return live ? { ...live, sequence: turn.sequence } : turn;
|
|
3152
|
+
})
|
|
3153
|
+
]);
|
|
3154
|
+
}
|
|
3155
|
+
function replaceWindow(current, page, liveRevisionAtStart, liveRevisions) {
|
|
3156
|
+
const pageById = new Map(page.map((turn) => [turn.turn_id, turn]));
|
|
3157
|
+
const newerLive = current.filter(
|
|
3158
|
+
(turn) => turn.turn_id.startsWith("local-user-") || (liveRevisions.byTurnId.get(turn.turn_id) ?? 0) > liveRevisionAtStart
|
|
3159
|
+
);
|
|
3160
|
+
return sortedUnique([
|
|
3161
|
+
...page,
|
|
3162
|
+
...newerLive.map((turn) => {
|
|
3163
|
+
const snapshot2 = pageById.get(turn.turn_id);
|
|
3164
|
+
return snapshot2 ? { ...turn, sequence: snapshot2.sequence } : turn;
|
|
3165
|
+
})
|
|
3166
|
+
]);
|
|
3167
|
+
}
|
|
3168
|
+
|
|
2703
3169
|
// src/schemas/message-utils.ts
|
|
2704
3170
|
var SYSTEM_REMINDER_PATTERN = /^<system-reminder>\s*[\s\S]*?\s*<\/system-reminder>$/i;
|
|
2705
3171
|
var SYSTEM_NOTIFICATION_PATTERN = /^<system-notification>\s*[\s\S]*?\s*<\/system-notification>$/i;
|
|
@@ -2708,6 +3174,36 @@ var LEGACY_FORK_CONTEXT_MARKERS = [
|
|
|
2708
3174
|
"\u53EF\u901A\u8FC7 `read_file` \u8BFB\u53D6\u3002",
|
|
2709
3175
|
"# \u4F60\u7684\u4EFB\u52A1"
|
|
2710
3176
|
];
|
|
3177
|
+
var INTERNAL_CHAT_ERROR_PATTERNS = [
|
|
3178
|
+
/\b(?:ENOENT|EACCES|EPERM)\b/i,
|
|
3179
|
+
/\[(?:Errno -?\d+|WinError \d+)\]/i,
|
|
3180
|
+
/\b(?:ECONNRESET|ECONNREFUSED|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|ENOTFOUND|EAI_AGAIN)\b/i,
|
|
3181
|
+
/\b(?:Connection refused|Connection reset by peer|All connection attempts failed|getaddrinfo failed)\b/i,
|
|
3182
|
+
/(?:^|[\s("'`])\/(?:root|home|Users|tmp|var|opt|etc)\//,
|
|
3183
|
+
/[A-Za-z]:\\(?:Users|Windows|Program Files|AppData)\\/i,
|
|
3184
|
+
/(?:^|\n)Traceback \(most recent call last\):/,
|
|
3185
|
+
/(?:^|\n)\s*at\s+\S+(?:\s+\(|:\d+:\d+)/,
|
|
3186
|
+
/(?:^|\n)File "[^"]+", line \d+/
|
|
3187
|
+
];
|
|
3188
|
+
var SAFE_CHAT_ERROR_PREFIXES = [
|
|
3189
|
+
"\u6C99\u76D2\u8FD0\u884C\u73AF\u5883\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
|
|
3190
|
+
"\u6A21\u578B\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u68C0\u67E5\u6A21\u578B\u914D\u7F6E\u540E\u91CD\u8BD5\u3002"
|
|
3191
|
+
];
|
|
3192
|
+
var GENERIC_CHAT_ERROR = "\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002";
|
|
3193
|
+
function chatErrorForDisplay(message, developer = false) {
|
|
3194
|
+
const normalized = message.trim();
|
|
3195
|
+
if (!developer) {
|
|
3196
|
+
const safePrefix = SAFE_CHAT_ERROR_PREFIXES.find((prefix) => normalized.startsWith(prefix));
|
|
3197
|
+
if (safePrefix) return safePrefix;
|
|
3198
|
+
}
|
|
3199
|
+
if (!developer && /no source matches this model/i.test(normalized)) {
|
|
3200
|
+
return "\u5F53\u524D\u6A21\u578B\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u6216\u91CD\u65B0\u9009\u62E9\u6A21\u578B\u3002";
|
|
3201
|
+
}
|
|
3202
|
+
if (!developer && INTERNAL_CHAT_ERROR_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
3203
|
+
return GENERIC_CHAT_ERROR;
|
|
3204
|
+
}
|
|
3205
|
+
return normalized || GENERIC_CHAT_ERROR;
|
|
3206
|
+
}
|
|
2711
3207
|
function normalizeMessageContent(content) {
|
|
2712
3208
|
if (typeof content === "string") return content;
|
|
2713
3209
|
if (Array.isArray(content)) return content;
|
|
@@ -2727,10 +3223,10 @@ function isLegacyForkContextText(text) {
|
|
|
2727
3223
|
const normalized = text.trim();
|
|
2728
3224
|
return LEGACY_FORK_CONTEXT_MARKERS.every((marker) => normalized.includes(marker));
|
|
2729
3225
|
}
|
|
2730
|
-
function isHiddenInternalMessage(message) {
|
|
3226
|
+
function isHiddenInternalMessage(message, options = {}) {
|
|
2731
3227
|
if (message.role !== "user") return false;
|
|
2732
3228
|
const text = getTextContent(normalizeMessageContent(message.content));
|
|
2733
|
-
return isSystemReminderText(text) || isSystemNotificationText(text) || isLegacyForkContextText(text);
|
|
3229
|
+
return isSystemReminderText(text) || isSystemNotificationText(text) || (options.includeLegacyForkContext ?? true) && isLegacyForkContextText(text);
|
|
2734
3230
|
}
|
|
2735
3231
|
function buildMessageContent(text, attachments) {
|
|
2736
3232
|
if (attachments.length === 0) return text;
|
|
@@ -2877,7 +3373,9 @@ function createInitialSessionState(sessionId) {
|
|
|
2877
3373
|
agentLoops: {},
|
|
2878
3374
|
activeCompaction: null,
|
|
2879
3375
|
replay: null,
|
|
2880
|
-
viewerRole: null
|
|
3376
|
+
viewerRole: null,
|
|
3377
|
+
nextBefore: null,
|
|
3378
|
+
loadingOlder: false
|
|
2881
3379
|
};
|
|
2882
3380
|
}
|
|
2883
3381
|
function parseAgentDescription(argumentsJson) {
|
|
@@ -2903,10 +3401,10 @@ function inferLoopStatusFromMessages(messages) {
|
|
|
2903
3401
|
}
|
|
2904
3402
|
function inferLoopStatusFromTurns(turns, messages) {
|
|
2905
3403
|
const latestAgentNotification = [...turns].reverse().flatMap((turn) => turn.blocks).find((block) => {
|
|
2906
|
-
if (block.type !== "system_notification" || !
|
|
3404
|
+
if (block.type !== "system_notification" || !isRecord(block.content)) return false;
|
|
2907
3405
|
return block.content.notification_type === "agent:start" || block.content.notification_type === "agent:end";
|
|
2908
3406
|
});
|
|
2909
|
-
if (latestAgentNotification?.type === "system_notification" &&
|
|
3407
|
+
if (latestAgentNotification?.type === "system_notification" && isRecord(latestAgentNotification.content)) {
|
|
2910
3408
|
const notificationType = latestAgentNotification.content.notification_type;
|
|
2911
3409
|
const status = latestAgentNotification.content.status;
|
|
2912
3410
|
if (notificationType === "agent:start" || status === "running") return "running";
|
|
@@ -2915,17 +3413,44 @@ function inferLoopStatusFromTurns(turns, messages) {
|
|
|
2915
3413
|
}
|
|
2916
3414
|
return inferLoopStatusFromMessages(messages);
|
|
2917
3415
|
}
|
|
2918
|
-
function
|
|
2919
|
-
|
|
3416
|
+
function toSelectionMap(value) {
|
|
3417
|
+
if (!isRecord(value)) return {};
|
|
3418
|
+
const entries = Object.entries(value).map(([questionKey, optionIndexes]) => {
|
|
3419
|
+
if (!Array.isArray(optionIndexes)) return null;
|
|
3420
|
+
const parsedIndexes = optionIndexes.map((item) => typeof item === "number" ? item : Number(item)).filter((item) => Number.isInteger(item));
|
|
3421
|
+
return [Number(questionKey), parsedIndexes];
|
|
3422
|
+
}).filter((entry) => entry !== null);
|
|
3423
|
+
return Object.fromEntries(entries);
|
|
3424
|
+
}
|
|
3425
|
+
function toCustomMap(value) {
|
|
3426
|
+
if (!isRecord(value)) return {};
|
|
3427
|
+
const entries = Object.entries(value).filter(([, text]) => typeof text === "string").map(([questionKey, text]) => [Number(questionKey), text]);
|
|
3428
|
+
return Object.fromEntries(entries);
|
|
3429
|
+
}
|
|
3430
|
+
function extractAskAnswers(turns) {
|
|
3431
|
+
const answers = {};
|
|
3432
|
+
for (const turn of turns) {
|
|
3433
|
+
for (const block of turn.blocks) {
|
|
3434
|
+
if (block.type !== "ask_user_answer" || typeof block.tool_call_id !== "string") continue;
|
|
3435
|
+
if (!isRecord(block.content)) continue;
|
|
3436
|
+
const note = typeof block.content.note === "string" ? block.content.note.trim() : "";
|
|
3437
|
+
answers[block.tool_call_id] = {
|
|
3438
|
+
selections: toSelectionMap(block.content.selections),
|
|
3439
|
+
custom: toCustomMap(block.content.custom),
|
|
3440
|
+
...note ? { note } : {}
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return answers;
|
|
2920
3445
|
}
|
|
2921
3446
|
function parentForkToolCallIdFromTurn(turn) {
|
|
2922
3447
|
if (typeof turn.parent_fork_tool_call_id === "string" && turn.parent_fork_tool_call_id.length > 0) {
|
|
2923
3448
|
return turn.parent_fork_tool_call_id;
|
|
2924
3449
|
}
|
|
2925
3450
|
for (const block of turn.blocks) {
|
|
2926
|
-
if (block.type !== "system_notification" || !
|
|
3451
|
+
if (block.type !== "system_notification" || !isRecord(block.content)) continue;
|
|
2927
3452
|
const metadata = block.content.metadata;
|
|
2928
|
-
if (!
|
|
3453
|
+
if (!isRecord(metadata)) continue;
|
|
2929
3454
|
const parentId = metadata.parent_fork_tool_call_id;
|
|
2930
3455
|
if (typeof parentId === "string" && parentId.length > 0) return parentId;
|
|
2931
3456
|
}
|
|
@@ -2942,16 +3467,16 @@ function buildMessageContent2(turn) {
|
|
|
2942
3467
|
}
|
|
2943
3468
|
function workspaceNotificationContent(turn) {
|
|
2944
3469
|
const block = turn.blocks.find(
|
|
2945
|
-
(candidate) => candidate.type === "system_notification" &&
|
|
3470
|
+
(candidate) => candidate.type === "system_notification" && isRecord(candidate.content) && candidate.content.notification_type === "workspace_change"
|
|
2946
3471
|
);
|
|
2947
|
-
if (!block || !
|
|
3472
|
+
if (!block || !isRecord(block.content)) return "";
|
|
2948
3473
|
const title = typeof block.content.title === "string" ? block.content.title.trim() : "";
|
|
2949
3474
|
const detail = typeof block.content.detail === "string" ? block.content.detail.trim() : "";
|
|
2950
3475
|
return [title ? `**${title}**` : "", detail].filter(Boolean).join("\n\n");
|
|
2951
3476
|
}
|
|
2952
3477
|
function askUserAnswerContent(turn) {
|
|
2953
3478
|
const answerBlock = turn.blocks.find((block) => block.type === "ask_user_answer");
|
|
2954
|
-
if (!answerBlock || !
|
|
3479
|
+
if (!answerBlock || !isRecord(answerBlock.content)) return null;
|
|
2955
3480
|
const answer = answerBlock.content.answer;
|
|
2956
3481
|
return typeof answer === "string" && answer.trim().length > 0 ? answer : null;
|
|
2957
3482
|
}
|
|
@@ -2971,6 +3496,19 @@ function extractModeFromBlocks(blocks) {
|
|
|
2971
3496
|
return null;
|
|
2972
3497
|
}
|
|
2973
3498
|
function projectionToMessage(turn) {
|
|
3499
|
+
if (turn.kind === "context") {
|
|
3500
|
+
const context = contextProjectionData(turn);
|
|
3501
|
+
if (!context) return null;
|
|
3502
|
+
return {
|
|
3503
|
+
role: "assistant",
|
|
3504
|
+
content: "",
|
|
3505
|
+
kind: "context",
|
|
3506
|
+
context,
|
|
3507
|
+
loop_name: turn.loop_id,
|
|
3508
|
+
entry_id: turn.turn_id,
|
|
3509
|
+
status: turn.status
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
2974
3512
|
if (turn.kind === "compaction" && turn.compaction_id) {
|
|
2975
3513
|
return {
|
|
2976
3514
|
role: "assistant",
|
|
@@ -3140,6 +3678,56 @@ function orderTurns(turns) {
|
|
|
3140
3678
|
}
|
|
3141
3679
|
return turns;
|
|
3142
3680
|
}
|
|
3681
|
+
function reconcileOptimisticUserTurns(currentTurns, incomingTurns) {
|
|
3682
|
+
return reconcileUserTurns(currentTurns, incomingTurns, true);
|
|
3683
|
+
}
|
|
3684
|
+
function reconcileHistoricalUserTurns(currentTurns, incomingTurns) {
|
|
3685
|
+
return reconcileUserTurns(currentTurns, incomingTurns, false);
|
|
3686
|
+
}
|
|
3687
|
+
function reconcileUserTurns(currentTurns, incomingTurns, allowLiveFallback) {
|
|
3688
|
+
const preservedTurns = preserveClientRenderIds(currentTurns, incomingTurns);
|
|
3689
|
+
const optimisticUserTurns = currentTurns.filter(
|
|
3690
|
+
(turn) => turn.role === "user" && (turn.kind ?? "message") === "message" && turn.turn_id.startsWith("local-user-")
|
|
3691
|
+
);
|
|
3692
|
+
const optimisticByRequestId = new Map(
|
|
3693
|
+
optimisticUserTurns.map((turn) => [
|
|
3694
|
+
turn.turn_id.slice("local-user-".length),
|
|
3695
|
+
turn
|
|
3696
|
+
])
|
|
3697
|
+
);
|
|
3698
|
+
const currentTurnIds = new Set(currentTurns.map((turn) => turn.turn_id));
|
|
3699
|
+
const claimedOptimisticIds = /* @__PURE__ */ new Set();
|
|
3700
|
+
const reconciled = preservedTurns.map((turn) => {
|
|
3701
|
+
const exactOptimistic = typeof turn.client_request_id === "string" ? optimisticByRequestId.get(turn.client_request_id) : void 0;
|
|
3702
|
+
if (exactOptimistic && !claimedOptimisticIds.has(exactOptimistic.turn_id) && isUserConfirmation(turn)) {
|
|
3703
|
+
claimedOptimisticIds.add(exactOptimistic.turn_id);
|
|
3704
|
+
return { ...turn, client_render_id: exactOptimistic.turn_id };
|
|
3705
|
+
}
|
|
3706
|
+
const fallbackOptimistic = optimisticUserTurns.find(
|
|
3707
|
+
(candidate) => !claimedOptimisticIds.has(candidate.turn_id)
|
|
3708
|
+
);
|
|
3709
|
+
if (allowLiveFallback && typeof turn.client_request_id !== "string" && fallbackOptimistic && isUserConfirmation(turn) && !turn.turn_id.startsWith("local-user-") && !currentTurnIds.has(turn.turn_id)) {
|
|
3710
|
+
claimedOptimisticIds.add(fallbackOptimistic.turn_id);
|
|
3711
|
+
return { ...turn, client_render_id: fallbackOptimistic.turn_id };
|
|
3712
|
+
}
|
|
3713
|
+
return turn;
|
|
3714
|
+
});
|
|
3715
|
+
return claimedOptimisticIds.size === 0 ? reconciled : reconciled.filter((turn) => !claimedOptimisticIds.has(turn.turn_id));
|
|
3716
|
+
}
|
|
3717
|
+
function isUserConfirmation(turn) {
|
|
3718
|
+
return turn.role === "user" && (turn.kind ?? "message") === "message" || turn.blocks.some((block) => block.type === "ask_user_answer");
|
|
3719
|
+
}
|
|
3720
|
+
function preserveClientRenderIds(currentTurns, incomingTurns) {
|
|
3721
|
+
const currentById = new Map(currentTurns.map((turn) => [turn.turn_id, turn]));
|
|
3722
|
+
return incomingTurns.map((turn) => {
|
|
3723
|
+
const current = currentById.get(turn.turn_id);
|
|
3724
|
+
if (current) {
|
|
3725
|
+
const currentRenderId = current.client_render_id;
|
|
3726
|
+
return turn.client_render_id || !currentRenderId ? turn : { ...turn, client_render_id: currentRenderId };
|
|
3727
|
+
}
|
|
3728
|
+
return turn;
|
|
3729
|
+
});
|
|
3730
|
+
}
|
|
3143
3731
|
function areAgentLoopsEqual(left, right) {
|
|
3144
3732
|
if (!left) return Object.keys(right).length === 0;
|
|
3145
3733
|
const leftEntries = Object.entries(left);
|
|
@@ -3151,7 +3739,7 @@ function areAgentLoopsEqual(left, right) {
|
|
|
3151
3739
|
});
|
|
3152
3740
|
}
|
|
3153
3741
|
function withTurns(state, turns) {
|
|
3154
|
-
const orderedTurns = orderTurns(turns);
|
|
3742
|
+
const orderedTurns = orderTurns(reconcileHistoricalUserTurns(state.turns, turns));
|
|
3155
3743
|
const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
|
|
3156
3744
|
const isWaitingForInput = orderedTurns.some(
|
|
3157
3745
|
(turn) => turn.tool_calls.some((toolCall) => toolCall.status === "awaiting_answer")
|
|
@@ -3161,14 +3749,31 @@ function withTurns(state, turns) {
|
|
|
3161
3749
|
const preservedErrors = lastTurnId ? state.messages.filter(
|
|
3162
3750
|
(m) => m.role === "error" && typeof m.entry_id === "string" && m.entry_id.startsWith(`${ERROR_ANCHOR_PREFIX}${lastTurnId}:`)
|
|
3163
3751
|
) : [];
|
|
3164
|
-
const
|
|
3752
|
+
const turnById = new Map(orderedTurns.map((turn) => [turn.turn_id, turn]));
|
|
3753
|
+
const messagesWithRenderIds = messages.map((message) => {
|
|
3754
|
+
if (!message.entry_id) return message;
|
|
3755
|
+
const renderId = turnById.get(message.entry_id)?.client_render_id;
|
|
3756
|
+
return renderId ? { ...message, render_id: renderId } : message;
|
|
3757
|
+
});
|
|
3758
|
+
const mergedMessages = preservedErrors.length > 0 ? [...messagesWithRenderIds, ...preservedErrors] : messagesWithRenderIds;
|
|
3165
3759
|
const latestMode = [...orderedTurns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
|
|
3760
|
+
const askAnswersFromHistory = extractAskAnswers(orderedTurns);
|
|
3761
|
+
const presentToolCallIds = new Set(
|
|
3762
|
+
orderedTurns.flatMap((turn) => turn.tool_calls.map((toolCall) => toolCall.id))
|
|
3763
|
+
);
|
|
3764
|
+
const askAnswers = { ...askAnswersFromHistory };
|
|
3765
|
+
for (const [toolCallId, answer] of Object.entries(state.askAnswers)) {
|
|
3766
|
+
if (!(toolCallId in askAnswers) && presentToolCallIds.has(toolCallId)) {
|
|
3767
|
+
askAnswers[toolCallId] = answer;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3166
3770
|
return {
|
|
3167
3771
|
...state,
|
|
3168
3772
|
turns: orderedTurns,
|
|
3169
3773
|
messages: mergedMessages,
|
|
3170
3774
|
agentLoops: stableAgentLoops,
|
|
3171
3775
|
activeCompaction,
|
|
3776
|
+
askAnswers,
|
|
3172
3777
|
mode: latestMode ?? state.mode,
|
|
3173
3778
|
// 历史恢复时 chat:end(paused) 可能已不在 socket replay 缓存中;
|
|
3174
3779
|
// awaiting_answer 是投影里的持久权威状态,必须据此恢复问答交互。
|
|
@@ -3176,9 +3781,9 @@ function withTurns(state, turns) {
|
|
|
3176
3781
|
};
|
|
3177
3782
|
}
|
|
3178
3783
|
var localTurnCounter = 0;
|
|
3179
|
-
function addUserMessage(state, content) {
|
|
3784
|
+
function addUserMessage(state, content, clientRequestId) {
|
|
3180
3785
|
localTurnCounter += 1;
|
|
3181
|
-
const turnId = `local-user-${Date.now()}-${localTurnCounter}`;
|
|
3786
|
+
const turnId = `local-user-${clientRequestId ?? `${Date.now()}-${localTurnCounter}`}`;
|
|
3182
3787
|
const optimisticTurn = {
|
|
3183
3788
|
id: turnId,
|
|
3184
3789
|
sequence: Math.max(0, ...state.turns.map((turn) => turn.sequence)) + 1,
|
|
@@ -3196,14 +3801,16 @@ function addUserMessage(state, content) {
|
|
|
3196
3801
|
}
|
|
3197
3802
|
function upsertTurn(state, turn) {
|
|
3198
3803
|
let existing = [...state.turns];
|
|
3199
|
-
|
|
3200
|
-
|
|
3804
|
+
const [nextTurn] = reconcileOptimisticUserTurns(existing, [turn]);
|
|
3805
|
+
const claimedOptimisticId = nextTurn.client_render_id;
|
|
3806
|
+
if (claimedOptimisticId?.startsWith("local-user-")) {
|
|
3807
|
+
existing = existing.filter((item) => item.turn_id !== claimedOptimisticId);
|
|
3201
3808
|
}
|
|
3202
|
-
const index = existing.findIndex((item) => item.turn_id ===
|
|
3809
|
+
const index = existing.findIndex((item) => item.turn_id === nextTurn.turn_id);
|
|
3203
3810
|
if (index >= 0) {
|
|
3204
|
-
existing[index] =
|
|
3811
|
+
existing[index] = nextTurn;
|
|
3205
3812
|
} else {
|
|
3206
|
-
existing.push(
|
|
3813
|
+
existing.push(nextTurn);
|
|
3207
3814
|
}
|
|
3208
3815
|
return withTurns(state, existing);
|
|
3209
3816
|
}
|
|
@@ -3216,13 +3823,7 @@ function applyTurnPatch(state, patch) {
|
|
|
3216
3823
|
if (lastSequence !== null && patch.sequence <= lastSequence) {
|
|
3217
3824
|
return state;
|
|
3218
3825
|
}
|
|
3219
|
-
|
|
3220
|
-
if (index >= 0) {
|
|
3221
|
-
existing[index] = nextTurn;
|
|
3222
|
-
} else {
|
|
3223
|
-
existing.push(nextTurn);
|
|
3224
|
-
}
|
|
3225
|
-
return withTurns(state, existing);
|
|
3826
|
+
return upsertTurn(state, { ...turn, sequence: patch.sequence });
|
|
3226
3827
|
}
|
|
3227
3828
|
function addErrorMessage(state, content) {
|
|
3228
3829
|
const anchorTurnId = state.turns[state.turns.length - 1]?.turn_id ?? null;
|
|
@@ -3254,19 +3855,26 @@ function markStreamingTurns(state, turnStatus, toolCallStatus) {
|
|
|
3254
3855
|
|
|
3255
3856
|
// src/session/agent-session.ts
|
|
3256
3857
|
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";
|
|
3257
|
-
var
|
|
3258
|
-
|
|
3259
|
-
|
|
3858
|
+
var COMMAND_RESTORE_RETRY_BASE_MS = 1e3;
|
|
3859
|
+
var COMMAND_RESTORE_RETRY_MAX_MS = 3e4;
|
|
3860
|
+
var fallbackRequestCounter = 0;
|
|
3861
|
+
function createClientRequestId() {
|
|
3862
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
3863
|
+
return globalThis.crypto.randomUUID();
|
|
3864
|
+
}
|
|
3865
|
+
fallbackRequestCounter += 1;
|
|
3866
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}-${fallbackRequestCounter}`;
|
|
3260
3867
|
}
|
|
3868
|
+
var OOM_KEYWORDS_RE = /(?:\b(?:exit_code|ExitedWith|exited with)|退出码)\D*(?<!\d)137(?!\d)/i;
|
|
3261
3869
|
function isOomText(value) {
|
|
3262
3870
|
return typeof value === "string" && OOM_KEYWORDS_RE.test(value);
|
|
3263
3871
|
}
|
|
3264
3872
|
function parseJsonRecord(value) {
|
|
3265
|
-
if (
|
|
3873
|
+
if (isRecord(value)) return value;
|
|
3266
3874
|
if (typeof value !== "string") return null;
|
|
3267
3875
|
try {
|
|
3268
3876
|
const parsed = JSON.parse(value);
|
|
3269
|
-
return
|
|
3877
|
+
return isRecord(parsed) ? parsed : null;
|
|
3270
3878
|
} catch {
|
|
3271
3879
|
return null;
|
|
3272
3880
|
}
|
|
@@ -3292,6 +3900,12 @@ function isOomResult(value) {
|
|
|
3292
3900
|
function isToolBridgeContent(value) {
|
|
3293
3901
|
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.action === "string";
|
|
3294
3902
|
}
|
|
3903
|
+
function isCursorInvalidError(error) {
|
|
3904
|
+
return typeof error === "object" && error !== null && "status" in error && error.status === 409;
|
|
3905
|
+
}
|
|
3906
|
+
function commandDeliveryKey(deliveryId) {
|
|
3907
|
+
return `tool-command:${deliveryId}`;
|
|
3908
|
+
}
|
|
3295
3909
|
function sessionStatusFromChatEnd(status) {
|
|
3296
3910
|
if (status === "paused") return "waiting_for_input";
|
|
3297
3911
|
if (status === "interrupted") return "interrupted";
|
|
@@ -3307,13 +3921,8 @@ function extractTerminalChatEndStatus(events) {
|
|
|
3307
3921
|
}
|
|
3308
3922
|
return null;
|
|
3309
3923
|
}
|
|
3310
|
-
function hasChatRunEvent2(events) {
|
|
3311
|
-
return events.some(
|
|
3312
|
-
(event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
3313
|
-
);
|
|
3314
|
-
}
|
|
3315
3924
|
function isUiMetaLike(value) {
|
|
3316
|
-
return
|
|
3925
|
+
return isRecord(value) && ("resourceHTML" in value || "resourceUri" in value || "resourceURI" in value);
|
|
3317
3926
|
}
|
|
3318
3927
|
var AgentSession = class _AgentSession {
|
|
3319
3928
|
sessionId;
|
|
@@ -3328,14 +3937,39 @@ var AgentSession = class _AgentSession {
|
|
|
3328
3937
|
patchFlushCancel = null;
|
|
3329
3938
|
joined = false;
|
|
3330
3939
|
joinPending = null;
|
|
3940
|
+
// bootstrap 与每次 reconnect 共用一条订阅生命周期队列。它们都会重置 join
|
|
3941
|
+
// 状态并临时设置 replay source,禁止并发,否则较早操作的 finally 会清掉
|
|
3942
|
+
// 较晚操作仍在使用的来源上下文。
|
|
3943
|
+
subscriptionQueue = Promise.resolve();
|
|
3944
|
+
replaceQueue = Promise.resolve();
|
|
3945
|
+
timelineEpoch = 0;
|
|
3946
|
+
pagingGeneration = 0;
|
|
3947
|
+
liveRevision = 0;
|
|
3948
|
+
modeRevision = 0;
|
|
3949
|
+
liveRevisions = /* @__PURE__ */ new Map();
|
|
3950
|
+
pendingOptimisticTurnIds = /* @__PURE__ */ new Set();
|
|
3951
|
+
/** Consecutive supplements are persisted as one authoritative user turn. */
|
|
3952
|
+
pendingAppendTurnId = null;
|
|
3953
|
+
olderPending = null;
|
|
3954
|
+
replaceInFlight = 0;
|
|
3331
3955
|
// 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
|
|
3332
3956
|
// 请求响应可能乱序回来,晚到的旧值会把已经退出的会话又显示成回放中;用
|
|
3333
3957
|
// 序号丢弃过期响应又会在失败时留下中间态。串行执行就没有这些情况:每个
|
|
3334
3958
|
// 响应都是当时最新的,失败也只影响自己。
|
|
3335
3959
|
replayQueue = Promise.resolve();
|
|
3960
|
+
// Historical snapshots and live commands share one delivery barrier. A
|
|
3961
|
+
// repair closes the barrier before joining, snapshots the raw command log
|
|
3962
|
+
// after the join, then releases buffered commands in socket arrival order.
|
|
3963
|
+
commandRestoreQueue = Promise.resolve();
|
|
3964
|
+
commandRecoveryGeneration = 0;
|
|
3965
|
+
commandRestoreRetryAttempt = 0;
|
|
3966
|
+
commandRestoreRetryHandle = null;
|
|
3967
|
+
bufferedCommandDeliveries = null;
|
|
3336
3968
|
pendingReplayMessage = null;
|
|
3337
3969
|
pendingReplayMode;
|
|
3970
|
+
pendingReplayKbIds;
|
|
3338
3971
|
recentChatEndAt = 0;
|
|
3972
|
+
subscriptionReplaySource = null;
|
|
3339
3973
|
disposed = false;
|
|
3340
3974
|
getAppContext = null;
|
|
3341
3975
|
sendPending = false;
|
|
@@ -3359,11 +3993,11 @@ var AgentSession = class _AgentSession {
|
|
|
3359
3993
|
const session = new _AgentSession(
|
|
3360
3994
|
sessionId,
|
|
3361
3995
|
{
|
|
3362
|
-
streamTokens: false,
|
|
3363
3996
|
emitEvent: readonlyError,
|
|
3364
3997
|
emitWithAck: async () => readonlyError(),
|
|
3365
3998
|
ensureConnected: readonlyError,
|
|
3366
|
-
|
|
3999
|
+
fetchTurnsPage: async () => ({ turns, nextBefore: null, currentMode: "executing" }),
|
|
4000
|
+
fetchCommands: async () => [],
|
|
3367
4001
|
// 静态历史没有回放身份可言,给个空详情即可,不要抛错——_bootstrap
|
|
3368
4002
|
// 不会跑到这里,但保持只读语义一致。
|
|
3369
4003
|
fetchSessionInfo: async () => ({ replay_state: null }),
|
|
@@ -3387,6 +4021,42 @@ var AgentSession = class _AgentSession {
|
|
|
3387
4021
|
getState() {
|
|
3388
4022
|
return this.state;
|
|
3389
4023
|
}
|
|
4024
|
+
get hasOlderHistory() {
|
|
4025
|
+
return this.state.nextBefore !== null;
|
|
4026
|
+
}
|
|
4027
|
+
/** Load the page immediately preceding the current display window. */
|
|
4028
|
+
loadOlderHistory(options = {}) {
|
|
4029
|
+
if (this.disposed) return Promise.resolve();
|
|
4030
|
+
if (this.olderPending) return this.olderPending;
|
|
4031
|
+
if (this.replaceInFlight > 0) {
|
|
4032
|
+
return this.replaceQueue.then(() => this.loadOlderHistory(options));
|
|
4033
|
+
}
|
|
4034
|
+
const before = this.state.nextBefore;
|
|
4035
|
+
if (!before) return Promise.resolve();
|
|
4036
|
+
const epoch = this.timelineEpoch;
|
|
4037
|
+
const generation = this.pagingGeneration;
|
|
4038
|
+
this.update((state) => ({ ...state, loadingOlder: true }));
|
|
4039
|
+
const request = this.runtime.fetchTurnsPage(this.sessionId, before).then((page) => {
|
|
4040
|
+
if (epoch !== this.timelineEpoch || generation !== this.pagingGeneration || this.state.nextBefore !== before) return;
|
|
4041
|
+
options.beforeCommit?.();
|
|
4042
|
+
this.update((state) => ({
|
|
4043
|
+
...withTurns(state, prependOlder(state.turns, page.turns)),
|
|
4044
|
+
nextBefore: page.nextBefore,
|
|
4045
|
+
loadingOlder: false
|
|
4046
|
+
}));
|
|
4047
|
+
}).catch((error) => {
|
|
4048
|
+
if (isCursorInvalidError(error) && epoch === this.timelineEpoch && generation === this.pagingGeneration && this.state.nextBefore === before) {
|
|
4049
|
+
this.timelineEpoch += 1;
|
|
4050
|
+
return this.replaceFromHistory();
|
|
4051
|
+
}
|
|
4052
|
+
}).finally(() => {
|
|
4053
|
+
if (this.olderPending !== request) return;
|
|
4054
|
+
this.olderPending = null;
|
|
4055
|
+
this.update((state) => ({ ...state, loadingOlder: false }));
|
|
4056
|
+
});
|
|
4057
|
+
this.olderPending = request;
|
|
4058
|
+
return request;
|
|
4059
|
+
}
|
|
3390
4060
|
/** 订阅状态变化。返回取消订阅函数。 */
|
|
3391
4061
|
subscribe(listener) {
|
|
3392
4062
|
this.listeners.add(listener);
|
|
@@ -3452,6 +4122,22 @@ var AgentSession = class _AgentSession {
|
|
|
3452
4122
|
this.sendPending = false;
|
|
3453
4123
|
}
|
|
3454
4124
|
}
|
|
4125
|
+
/** Queue a plain-text prompt while a turn is streaming. */
|
|
4126
|
+
async queue(content) {
|
|
4127
|
+
const text = content.trim();
|
|
4128
|
+
if (!text) return false;
|
|
4129
|
+
this.runtime.ensureConnected();
|
|
4130
|
+
this.runtime.emitEvent("chat:append", { session_id: this.sessionId, message: text });
|
|
4131
|
+
return true;
|
|
4132
|
+
}
|
|
4133
|
+
async listQueue() {
|
|
4134
|
+
const response = await this.runtime.emitWithAck("chat:queue:list", { session_id: this.sessionId }, 1e4);
|
|
4135
|
+
return Array.isArray(response?.items) ? response.items : [];
|
|
4136
|
+
}
|
|
4137
|
+
async cancelQueued(id) {
|
|
4138
|
+
const response = await this.runtime.emitWithAck("chat:queue:cancel", { session_id: this.sessionId, id }, 1e4);
|
|
4139
|
+
return response?.cancelled === true;
|
|
4140
|
+
}
|
|
3455
4141
|
async sendUnlocked(content, options) {
|
|
3456
4142
|
await this.ensureIdleBeforeSend();
|
|
3457
4143
|
if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionPending) {
|
|
@@ -3464,7 +4150,7 @@ var AgentSession = class _AgentSession {
|
|
|
3464
4150
|
if (this.getAppContext) {
|
|
3465
4151
|
try {
|
|
3466
4152
|
const rawContext = await this.getAppContext();
|
|
3467
|
-
if (!
|
|
4153
|
+
if (!isRecord(rawContext)) {
|
|
3468
4154
|
throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE JSON \u5BF9\u8C61");
|
|
3469
4155
|
}
|
|
3470
4156
|
const serialized = JSON.stringify(rawContext);
|
|
@@ -3472,7 +4158,7 @@ var AgentSession = class _AgentSession {
|
|
|
3472
4158
|
throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE\u53EF\u5E8F\u5217\u5316\u7684 JSON \u5BF9\u8C61");
|
|
3473
4159
|
}
|
|
3474
4160
|
const normalized = JSON.parse(serialized);
|
|
3475
|
-
if (!
|
|
4161
|
+
if (!isRecord(normalized)) {
|
|
3476
4162
|
throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE JSON \u5BF9\u8C61");
|
|
3477
4163
|
}
|
|
3478
4164
|
appContext = normalized;
|
|
@@ -3484,15 +4170,15 @@ var AgentSession = class _AgentSession {
|
|
|
3484
4170
|
}
|
|
3485
4171
|
const askUserAnswer = options.askUserAnswer;
|
|
3486
4172
|
let optimisticTurnId = null;
|
|
4173
|
+
const clientRequestId = createClientRequestId();
|
|
3487
4174
|
let optimisticAskAnswer;
|
|
3488
4175
|
let previousAskAnswer;
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
}
|
|
4176
|
+
this.update((state) => {
|
|
4177
|
+
const next = addUserMessage(state, content, clientRequestId);
|
|
4178
|
+
optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
|
|
4179
|
+
return next;
|
|
4180
|
+
});
|
|
4181
|
+
if (optimisticTurnId) this.markOptimisticTurnPending(optimisticTurnId);
|
|
3496
4182
|
this.update((s) => ({ ...s, isStreaming: true }));
|
|
3497
4183
|
if (askUserAnswer && typeof askUserAnswer.tool_call_id === "string") {
|
|
3498
4184
|
const { tool_call_id, ...rest } = askUserAnswer;
|
|
@@ -3506,17 +4192,20 @@ var AgentSession = class _AgentSession {
|
|
|
3506
4192
|
if (!options.replayDecision) {
|
|
3507
4193
|
this.pendingReplayMessage = content;
|
|
3508
4194
|
this.pendingReplayMode = options.mode;
|
|
4195
|
+
this.pendingReplayKbIds = options.kbIds;
|
|
3509
4196
|
}
|
|
3510
4197
|
const payload = {
|
|
3511
4198
|
session_id: this.sessionId,
|
|
3512
4199
|
message: content,
|
|
4200
|
+
client_request_id: clientRequestId,
|
|
3513
4201
|
mode: options.mode,
|
|
3514
4202
|
askuser_answer: askUserAnswer,
|
|
3515
4203
|
model: options.model,
|
|
3516
4204
|
thinking_override: options.thinkingOverride,
|
|
3517
4205
|
whatif: options.whatif,
|
|
3518
4206
|
replay_decision: options.replayDecision,
|
|
3519
|
-
app_context: appContext
|
|
4207
|
+
app_context: appContext,
|
|
4208
|
+
kb_ids: options.kbIds && options.kbIds.length > 0 ? options.kbIds : void 0
|
|
3520
4209
|
};
|
|
3521
4210
|
try {
|
|
3522
4211
|
await this.ensureJoined();
|
|
@@ -3552,6 +4241,7 @@ var AgentSession = class _AgentSession {
|
|
|
3552
4241
|
rollbackOptimisticSend(optimisticTurnId, askUserAnswer, optimisticAskAnswer, previousAskAnswer, message) {
|
|
3553
4242
|
this.pendingReplayMessage = null;
|
|
3554
4243
|
this.pendingReplayMode = void 0;
|
|
4244
|
+
this.pendingReplayKbIds = void 0;
|
|
3555
4245
|
this.update((current) => {
|
|
3556
4246
|
let next = optimisticTurnId ? withTurns(
|
|
3557
4247
|
current,
|
|
@@ -3569,16 +4259,51 @@ var AgentSession = class _AgentSession {
|
|
|
3569
4259
|
}
|
|
3570
4260
|
return addErrorMessage({ ...next, isStreaming: false }, message);
|
|
3571
4261
|
});
|
|
4262
|
+
if (optimisticTurnId) {
|
|
4263
|
+
this.pendingOptimisticTurnIds.delete(optimisticTurnId);
|
|
4264
|
+
this.liveRevisions.delete(optimisticTurnId);
|
|
4265
|
+
if (this.pendingAppendTurnId === optimisticTurnId) this.pendingAppendTurnId = null;
|
|
4266
|
+
}
|
|
3572
4267
|
}
|
|
3573
4268
|
/** 智能体运行期间追加补充说明(不打断当前回复)。 */
|
|
3574
4269
|
append(text) {
|
|
3575
4270
|
this.runtime.ensureConnected();
|
|
3576
|
-
|
|
3577
|
-
|
|
4271
|
+
const existingAppendId = this.pendingAppendTurnId;
|
|
4272
|
+
let optimisticTurnId = existingAppendId;
|
|
4273
|
+
this.update((state) => {
|
|
4274
|
+
const existing = existingAppendId ? state.turns.find((turn) => turn.turn_id === existingAppendId) : void 0;
|
|
4275
|
+
if (existing && existing.role === "user") {
|
|
4276
|
+
const blocks = existing.blocks.map(
|
|
4277
|
+
(block, index) => index === 0 && block.type === "text" ? { ...block, content: `${block.content}
|
|
4278
|
+
${text}` } : block
|
|
4279
|
+
);
|
|
4280
|
+
return withTurns(state, state.turns.map(
|
|
4281
|
+
(turn) => turn.turn_id === existingAppendId ? { ...turn, blocks } : turn
|
|
4282
|
+
));
|
|
4283
|
+
}
|
|
4284
|
+
const next = addUserMessage(state, text, createClientRequestId());
|
|
4285
|
+
optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
|
|
4286
|
+
return next;
|
|
4287
|
+
});
|
|
4288
|
+
if (optimisticTurnId) {
|
|
4289
|
+
this.pendingAppendTurnId = optimisticTurnId;
|
|
4290
|
+
if (!this.pendingOptimisticTurnIds.has(optimisticTurnId)) {
|
|
4291
|
+
this.markOptimisticTurnPending(optimisticTurnId);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
this.runtime.emitEvent("chat:append", {
|
|
4295
|
+
session_id: this.sessionId,
|
|
4296
|
+
message: text,
|
|
4297
|
+
...optimisticTurnId ? { client_request_id: optimisticTurnId.slice("local-user-".length) } : {}
|
|
4298
|
+
});
|
|
3578
4299
|
}
|
|
3579
4300
|
/** 停止当前回复。 */
|
|
3580
4301
|
stop() {
|
|
3581
|
-
return this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3).then((response) =>
|
|
4302
|
+
return this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3).then((response) => {
|
|
4303
|
+
const result = response ?? {};
|
|
4304
|
+
if (result.status !== "accepted") this.manualCompactionPending = false;
|
|
4305
|
+
return result;
|
|
4306
|
+
});
|
|
3582
4307
|
}
|
|
3583
4308
|
/** 手动触发上下文压缩。 */
|
|
3584
4309
|
compact() {
|
|
@@ -3598,8 +4323,10 @@ var AgentSession = class _AgentSession {
|
|
|
3598
4323
|
trigger: "manual"
|
|
3599
4324
|
}
|
|
3600
4325
|
}));
|
|
3601
|
-
return this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4).then((response) => response ?? {}).
|
|
4326
|
+
return this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4).then((response) => response ?? {}).catch((error) => {
|
|
3602
4327
|
this.manualCompactionPending = false;
|
|
4328
|
+
throw error;
|
|
4329
|
+
}).finally(() => {
|
|
3603
4330
|
if (this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
|
|
3604
4331
|
this.update((state) => ({ ...state, activeCompaction: null }));
|
|
3605
4332
|
}
|
|
@@ -3638,10 +4365,20 @@ var AgentSession = class _AgentSession {
|
|
|
3638
4365
|
if (this.disposed) return;
|
|
3639
4366
|
if (!this.runtime.onDispose(this)) return;
|
|
3640
4367
|
this.disposed = true;
|
|
4368
|
+
this.timelineEpoch += 1;
|
|
4369
|
+
this.invalidateOlderPaging();
|
|
4370
|
+
this.state = { ...this.state, nextBefore: null, loadingOlder: false };
|
|
3641
4371
|
this.cancelPatchFlush();
|
|
3642
4372
|
this.listeners.clear();
|
|
3643
4373
|
this.emitter.clear();
|
|
3644
4374
|
this.commands.clear();
|
|
4375
|
+
this.commandRecoveryGeneration += 1;
|
|
4376
|
+
this.commandRestoreRetryAttempt = 0;
|
|
4377
|
+
this.bufferedCommandDeliveries = null;
|
|
4378
|
+
if (this.commandRestoreRetryHandle !== null) {
|
|
4379
|
+
globalThis.clearTimeout(this.commandRestoreRetryHandle);
|
|
4380
|
+
this.commandRestoreRetryHandle = null;
|
|
4381
|
+
}
|
|
3645
4382
|
}
|
|
3646
4383
|
get isDisposed() {
|
|
3647
4384
|
return this.disposed;
|
|
@@ -3653,27 +4390,55 @@ var AgentSession = class _AgentSession {
|
|
|
3653
4390
|
this.update((s) => ({ ...s, connection }));
|
|
3654
4391
|
}
|
|
3655
4392
|
/** @internal socket 重连后由 Hub 调用:重新加入房间并补数。 */
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
this.
|
|
3659
|
-
this.
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
this.
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
4393
|
+
_rejoinAfterReconnect() {
|
|
4394
|
+
this.timelineEpoch += 1;
|
|
4395
|
+
this.invalidateOlderPaging();
|
|
4396
|
+
this.update((state) => ({ ...state, nextBefore: null, loadingOlder: false }));
|
|
4397
|
+
const reconnectLiveRevision = this.liveRevision;
|
|
4398
|
+
const reconnectModeRevision = this.modeRevision;
|
|
4399
|
+
const commandRecoveryGeneration = this.beginCommandRecovery();
|
|
4400
|
+
return this.enqueueSubscription(async () => {
|
|
4401
|
+
this.joined = false;
|
|
4402
|
+
this.joinPending = null;
|
|
4403
|
+
try {
|
|
4404
|
+
this.subscriptionReplaySource = "reconnect_replay";
|
|
4405
|
+
try {
|
|
4406
|
+
await this.ensureJoined(reconnectLiveRevision);
|
|
4407
|
+
} finally {
|
|
4408
|
+
this.subscriptionReplaySource = null;
|
|
4409
|
+
this.scheduleHistoricalCommandRestore(commandRecoveryGeneration);
|
|
4410
|
+
}
|
|
4411
|
+
await Promise.all([
|
|
4412
|
+
this.replaceFromHistory({
|
|
4413
|
+
liveRevisionAtStart: reconnectLiveRevision,
|
|
4414
|
+
modeRevisionAtStart: reconnectModeRevision
|
|
4415
|
+
}),
|
|
4416
|
+
this.syncReplayFromSession()
|
|
4417
|
+
]);
|
|
4418
|
+
} catch {
|
|
4419
|
+
}
|
|
4420
|
+
});
|
|
3671
4421
|
}
|
|
3672
4422
|
/** @internal 首次连接:加载历史 + 加入房间。 */
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
4423
|
+
_bootstrap() {
|
|
4424
|
+
const commandRecoveryGeneration = this.beginCommandRecovery();
|
|
4425
|
+
return this.enqueueSubscription(async () => {
|
|
4426
|
+
await this.replaceFromHistory({ suppressErrors: false });
|
|
4427
|
+
this.subscriptionReplaySource = "bootstrap_replay";
|
|
4428
|
+
try {
|
|
4429
|
+
await this.ensureJoined();
|
|
4430
|
+
} finally {
|
|
4431
|
+
this.subscriptionReplaySource = null;
|
|
4432
|
+
}
|
|
4433
|
+
this.scheduleHistoricalCommandRestore(commandRecoveryGeneration);
|
|
4434
|
+
await this.syncReplayFromSession();
|
|
4435
|
+
});
|
|
4436
|
+
}
|
|
4437
|
+
/** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
|
|
4438
|
+
enqueueSubscription(operation) {
|
|
4439
|
+
const run = this.subscriptionQueue.then(operation);
|
|
4440
|
+
this.subscriptionQueue = run.catch(() => void 0);
|
|
4441
|
+
return run;
|
|
3677
4442
|
}
|
|
3678
4443
|
/** 改回放是 owner 专属(服务端 PATCH 走 _get_owned_session)。 */
|
|
3679
4444
|
get canControlReplay() {
|
|
@@ -3730,10 +4495,16 @@ var AgentSession = class _AgentSession {
|
|
|
3730
4495
|
this.applyTurn(turn);
|
|
3731
4496
|
}
|
|
3732
4497
|
/** @internal 历史回放通道(event_format: raw)。 */
|
|
3733
|
-
_handleTurnEvents(events) {
|
|
4498
|
+
_handleTurnEvents(events, replayed = false) {
|
|
3734
4499
|
if (!events.length) return;
|
|
4500
|
+
if (events.some(
|
|
4501
|
+
(event) => event.type === "compaction:end" || event.type === "compaction:error" || event.type === "compaction:abort"
|
|
4502
|
+
)) {
|
|
4503
|
+
this.manualCompactionPending = false;
|
|
4504
|
+
}
|
|
4505
|
+
this.dispatchRawCommands(events);
|
|
3735
4506
|
const terminalChatEndStatus = extractTerminalChatEndStatus(events);
|
|
3736
|
-
if (!terminalChatEndStatus &&
|
|
4507
|
+
if (!terminalChatEndStatus && hasChatRunEvent(events) && this.state.status !== "waiting_for_input") {
|
|
3737
4508
|
this.markRunningIfIdle();
|
|
3738
4509
|
}
|
|
3739
4510
|
if (this.state.turns.length) {
|
|
@@ -3742,7 +4513,10 @@ var AgentSession = class _AgentSession {
|
|
|
3742
4513
|
const updates = this.projectionBuilder.processBatch(events);
|
|
3743
4514
|
for (const update of updates) {
|
|
3744
4515
|
if (update.kind === "upsert") {
|
|
3745
|
-
this.applyTurn(
|
|
4516
|
+
this.applyTurn(
|
|
4517
|
+
update.turn,
|
|
4518
|
+
replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
|
|
4519
|
+
);
|
|
3746
4520
|
} else if (update.kind === "workspace_changed") {
|
|
3747
4521
|
this.emitter.emit("workspaceChanged", {});
|
|
3748
4522
|
}
|
|
@@ -3797,16 +4571,26 @@ var AgentSession = class _AgentSession {
|
|
|
3797
4571
|
}
|
|
3798
4572
|
/** @internal */
|
|
3799
4573
|
_handleRewind(checkpointId) {
|
|
3800
|
-
this.
|
|
3801
|
-
|
|
4574
|
+
this.timelineEpoch += 1;
|
|
4575
|
+
this.invalidateOlderPaging();
|
|
4576
|
+
this.update((s) => ({
|
|
4577
|
+
...s,
|
|
4578
|
+
mode: null,
|
|
4579
|
+
errorMessage: null,
|
|
4580
|
+
nextBefore: null,
|
|
4581
|
+
loadingOlder: false
|
|
4582
|
+
}));
|
|
4583
|
+
void this.replaceFromHistory();
|
|
3802
4584
|
this.emitter.emit("rewind", { checkpointId });
|
|
3803
4585
|
}
|
|
3804
4586
|
/** @internal */
|
|
3805
4587
|
_handleReplayMismatch(data) {
|
|
3806
4588
|
const message = this.pendingReplayMessage ?? data.actual_message ?? "";
|
|
3807
4589
|
const mode = this.pendingReplayMode;
|
|
4590
|
+
const kbIds = this.pendingReplayKbIds;
|
|
3808
4591
|
this.pendingReplayMessage = null;
|
|
3809
4592
|
this.pendingReplayMode = void 0;
|
|
4593
|
+
this.pendingReplayKbIds = void 0;
|
|
3810
4594
|
const respond = (decision) => {
|
|
3811
4595
|
void this.runtime.emitWithAck(
|
|
3812
4596
|
"chat:send",
|
|
@@ -3814,7 +4598,8 @@ var AgentSession = class _AgentSession {
|
|
|
3814
4598
|
session_id: this.sessionId,
|
|
3815
4599
|
message,
|
|
3816
4600
|
mode,
|
|
3817
|
-
replay_decision: decision
|
|
4601
|
+
replay_decision: decision,
|
|
4602
|
+
kb_ids: kbIds && kbIds.length > 0 ? kbIds : void 0
|
|
3818
4603
|
},
|
|
3819
4604
|
3e4
|
|
3820
4605
|
).then((response) => {
|
|
@@ -3855,9 +4640,9 @@ var AgentSession = class _AgentSession {
|
|
|
3855
4640
|
detail,
|
|
3856
4641
|
status,
|
|
3857
4642
|
loopId,
|
|
3858
|
-
metadata:
|
|
4643
|
+
metadata: isRecord(notification.metadata) ? notification.metadata : void 0
|
|
3859
4644
|
});
|
|
3860
|
-
if (notificationType === "bg:started" &&
|
|
4645
|
+
if (notificationType === "bg:started" && isRecord(notification.metadata)) {
|
|
3861
4646
|
const taskId = typeof notification.metadata.task_id === "string" ? notification.metadata.task_id : "";
|
|
3862
4647
|
if (taskId) {
|
|
3863
4648
|
this.emitter.emit("backgroundTask", {
|
|
@@ -3879,12 +4664,12 @@ var AgentSession = class _AgentSession {
|
|
|
3879
4664
|
this.emitter.emit("artifact", event);
|
|
3880
4665
|
}
|
|
3881
4666
|
/** @internal 加入房间的应答里带 is_running,据此校准流式状态。 */
|
|
3882
|
-
_applyRunningState(isRunning) {
|
|
4667
|
+
_applyRunningState(isRunning, liveRevisionAtJoin = this.liveRevision) {
|
|
3883
4668
|
if (isRunning) {
|
|
3884
4669
|
this.update((s) => ({ ...s, status: "running", isStreaming: true }));
|
|
3885
4670
|
} else if (this.state.isStreaming) {
|
|
3886
4671
|
this.update((s) => ({ ...s, isStreaming: false }));
|
|
3887
|
-
void this.
|
|
4672
|
+
void this.replaceFromHistory({ liveRevisionAtStart: liveRevisionAtJoin });
|
|
3888
4673
|
}
|
|
3889
4674
|
}
|
|
3890
4675
|
// ---------------------------------------------------------------- 内部实现
|
|
@@ -3905,14 +4690,36 @@ var AgentSession = class _AgentSession {
|
|
|
3905
4690
|
completeChat(status) {
|
|
3906
4691
|
this.finishStreaming(status);
|
|
3907
4692
|
this.recentChatEndAt = Date.now();
|
|
3908
|
-
void this.
|
|
4693
|
+
void this.replaceFromHistory();
|
|
3909
4694
|
this.emitter.emit("chatEnd", { status });
|
|
3910
4695
|
}
|
|
3911
|
-
|
|
4696
|
+
markTurnFresh(turnId) {
|
|
4697
|
+
this.liveRevision += 1;
|
|
4698
|
+
this.liveRevisions.set(turnId, this.liveRevision);
|
|
4699
|
+
}
|
|
4700
|
+
markOptimisticTurnPending(turnId) {
|
|
4701
|
+
this.pendingOptimisticTurnIds.add(turnId);
|
|
4702
|
+
this.markTurnFresh(turnId);
|
|
4703
|
+
}
|
|
4704
|
+
pruneOptimisticLiveRevisions() {
|
|
4705
|
+
const retained = new Set(
|
|
4706
|
+
this.state.turns.map((turn) => turn.turn_id).filter((turnId) => turnId.startsWith("local-user-"))
|
|
4707
|
+
);
|
|
4708
|
+
for (const turnId of this.liveRevisions.keys()) {
|
|
4709
|
+
if (turnId.startsWith("local-user-") && !retained.has(turnId)) {
|
|
4710
|
+
this.pendingOptimisticTurnIds.delete(turnId);
|
|
4711
|
+
this.liveRevisions.delete(turnId);
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
applyTurn(turn, source = "live") {
|
|
3912
4716
|
this.flushPatches();
|
|
4717
|
+
this.markTurnFresh(turn.turn_id);
|
|
4718
|
+
if (extractModeFromBlocks(turn.blocks)) this.modeRevision += 1;
|
|
3913
4719
|
const previous = this.state;
|
|
3914
4720
|
this.update((s) => upsertTurn(s, turn));
|
|
3915
|
-
this.
|
|
4721
|
+
this.pruneOptimisticLiveRevisions();
|
|
4722
|
+
this.emitTurnDerivedEvents(previous, turn, source);
|
|
3916
4723
|
}
|
|
3917
4724
|
schedulePatchFlush() {
|
|
3918
4725
|
if (this.patchFlushHandle != null) return;
|
|
@@ -3945,11 +4752,15 @@ var AgentSession = class _AgentSession {
|
|
|
3945
4752
|
this.pendingTurnPatches.clear();
|
|
3946
4753
|
for (const patch of patches) {
|
|
3947
4754
|
const previous = this.state;
|
|
3948
|
-
|
|
4755
|
+
const next = applyTurnPatch(previous, patch);
|
|
4756
|
+
if (next === previous) continue;
|
|
4757
|
+
this.liveRevision += 1;
|
|
4758
|
+
this.liveRevisions.set(patch.turn_id, this.liveRevision);
|
|
3949
4759
|
const turn = patch.data.turn;
|
|
3950
|
-
if (turn)
|
|
3951
|
-
|
|
3952
|
-
|
|
4760
|
+
if (turn && extractModeFromBlocks(turn.blocks)) this.modeRevision += 1;
|
|
4761
|
+
this.setState(next);
|
|
4762
|
+
this.pruneOptimisticLiveRevisions();
|
|
4763
|
+
if (turn) this.emitTurnDerivedEvents(previous, turn);
|
|
3953
4764
|
}
|
|
3954
4765
|
if (this.patchFlushCancel) {
|
|
3955
4766
|
this.patchFlushCancel();
|
|
@@ -3958,7 +4769,7 @@ var AgentSession = class _AgentSession {
|
|
|
3958
4769
|
}
|
|
3959
4770
|
}
|
|
3960
4771
|
/** turn 数据落地后统一派生语义事件(指令 / 工具调用 / 消息 / 模式 / 预览 / OOM)。 */
|
|
3961
|
-
emitTurnDerivedEvents(previous, turn) {
|
|
4772
|
+
emitTurnDerivedEvents(previous, turn, source = "live") {
|
|
3962
4773
|
const mode = extractModeFromBlocks(turn.blocks);
|
|
3963
4774
|
if (mode && previous.mode !== mode) {
|
|
3964
4775
|
this.emitter.emit("modeChange", { mode });
|
|
@@ -3987,7 +4798,7 @@ var AgentSession = class _AgentSession {
|
|
|
3987
4798
|
title: ui.title ?? "\u5DE5\u5177\u9884\u89C8"
|
|
3988
4799
|
});
|
|
3989
4800
|
}
|
|
3990
|
-
if (block.type === "system_notification" &&
|
|
4801
|
+
if (block.type === "system_notification" && isRecord(block.content)) {
|
|
3991
4802
|
this._handleSystemNotification(turn.loop_id, block.content);
|
|
3992
4803
|
}
|
|
3993
4804
|
}
|
|
@@ -4007,7 +4818,7 @@ var AgentSession = class _AgentSession {
|
|
|
4007
4818
|
const finished = toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled";
|
|
4008
4819
|
if (finished && !this.finishedToolCallIds.has(toolCall.id)) {
|
|
4009
4820
|
this.finishedToolCallIds.add(toolCall.id);
|
|
4010
|
-
this.emitter.emit("toolResult", { toolCall: info, turn });
|
|
4821
|
+
this.emitter.emit("toolResult", { toolCall: info, turn, source });
|
|
4011
4822
|
if (isOomResult(toolCall.result)) {
|
|
4012
4823
|
this.notifySandboxOom(toolCall.id);
|
|
4013
4824
|
}
|
|
@@ -4024,17 +4835,36 @@ var AgentSession = class _AgentSession {
|
|
|
4024
4835
|
dispatchCommand(turn, block) {
|
|
4025
4836
|
if (!isToolBridgeContent(block.content)) return;
|
|
4026
4837
|
const { action, payload } = block.content;
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4838
|
+
const deliveryKey = block.delivery_id ? commandDeliveryKey(block.delivery_id) : `legacy-tool-command:${turn.turn_id}:${block.tool_call_id ?? ""}:${action}:${JSON.stringify(payload)}`;
|
|
4839
|
+
this.deliverCommand(deliveryKey, action, payload, block.tool_call_id ?? void 0);
|
|
4840
|
+
}
|
|
4841
|
+
dispatchRawCommands(events) {
|
|
4842
|
+
for (const event of events) {
|
|
4843
|
+
if (event.type !== "loop:tool_bridge") continue;
|
|
4844
|
+
const deliveryId = String(event.payload.delivery_id ?? "");
|
|
4845
|
+
const bridge = event.payload.bridge;
|
|
4846
|
+
if (!deliveryId || !isToolBridgeContent(bridge)) continue;
|
|
4847
|
+
this.deliverCommand(
|
|
4848
|
+
commandDeliveryKey(deliveryId),
|
|
4849
|
+
bridge.action,
|
|
4850
|
+
bridge.payload,
|
|
4851
|
+
String(event.payload.tool_call_id ?? "") || void 0
|
|
4852
|
+
);
|
|
4032
4853
|
}
|
|
4033
|
-
|
|
4854
|
+
}
|
|
4855
|
+
deliverCommand(deliveryKey, action, payload, toolCallId) {
|
|
4856
|
+
if (this.bufferedCommandDeliveries) {
|
|
4857
|
+
this.bufferedCommandDeliveries.push({ deliveryKey, action, payload, toolCallId });
|
|
4858
|
+
return;
|
|
4859
|
+
}
|
|
4860
|
+
this.deliverCommandNow({ deliveryKey, action, payload, toolCallId });
|
|
4861
|
+
}
|
|
4862
|
+
deliverCommandNow(command) {
|
|
4863
|
+
const { deliveryKey, action, payload, toolCallId } = command;
|
|
4034
4864
|
if (this.deliveredCommandKeys.has(deliveryKey)) return;
|
|
4035
4865
|
this.deliveredCommandKeys.add(deliveryKey);
|
|
4036
|
-
const meta = { toolCallId
|
|
4037
|
-
this.emitter.emit("command", { action, payload, toolCallId
|
|
4866
|
+
const meta = { toolCallId };
|
|
4867
|
+
this.emitter.emit("command", { action, payload, toolCallId });
|
|
4038
4868
|
this.commands.dispatch(action, payload, meta);
|
|
4039
4869
|
}
|
|
4040
4870
|
notifySandboxOom(key) {
|
|
@@ -4043,49 +4873,146 @@ var AgentSession = class _AgentSession {
|
|
|
4043
4873
|
this.update((s) => addErrorMessage(s, OOM_MESSAGE));
|
|
4044
4874
|
this.emitter.emit("sandboxOom", { toolCallId: key });
|
|
4045
4875
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
const
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4876
|
+
replaceFromHistory(options = {}) {
|
|
4877
|
+
if (this.disposed) return Promise.resolve();
|
|
4878
|
+
const operationEpoch = this.timelineEpoch;
|
|
4879
|
+
const liveRevisionAtStart = options.liveRevisionAtStart ?? this.liveRevision;
|
|
4880
|
+
const modeRevisionAtStart = options.modeRevisionAtStart ?? this.modeRevision;
|
|
4881
|
+
this.invalidateOlderPaging();
|
|
4882
|
+
this.replaceInFlight += 1;
|
|
4883
|
+
const operation = async () => {
|
|
4884
|
+
let previousNextBefore = null;
|
|
4885
|
+
try {
|
|
4886
|
+
if (this.disposed || operationEpoch !== this.timelineEpoch) return;
|
|
4887
|
+
this.invalidateOlderPaging();
|
|
4888
|
+
previousNextBefore = this.state.nextBefore;
|
|
4889
|
+
this.update((state) => ({ ...state, nextBefore: null, loadingOlder: false }));
|
|
4890
|
+
const page = await this.runtime.fetchTurnsPage(this.sessionId);
|
|
4891
|
+
if (this.disposed || operationEpoch !== this.timelineEpoch) return;
|
|
4892
|
+
if (!this.joined) this.seedToolCallDeliveryBaseline(page.turns);
|
|
4893
|
+
this.update((state) => {
|
|
4894
|
+
const next = withTurns(
|
|
4895
|
+
state,
|
|
4896
|
+
replaceWindow(state.turns, page.turns, liveRevisionAtStart, {
|
|
4897
|
+
current: this.liveRevision,
|
|
4898
|
+
byTurnId: this.liveRevisions
|
|
4899
|
+
})
|
|
4900
|
+
);
|
|
4901
|
+
return {
|
|
4902
|
+
...next,
|
|
4903
|
+
mode: this.modeRevision === modeRevisionAtStart ? page.currentMode : state.mode,
|
|
4904
|
+
nextBefore: page.nextBefore,
|
|
4905
|
+
loadingOlder: false
|
|
4906
|
+
};
|
|
4907
|
+
});
|
|
4908
|
+
this.pruneOptimisticLiveRevisions();
|
|
4909
|
+
this.projectionBuilder._seedTurns(this.state.turns);
|
|
4910
|
+
} catch (error) {
|
|
4911
|
+
if (!this.disposed && operationEpoch === this.timelineEpoch) {
|
|
4912
|
+
this.update((state) => ({
|
|
4913
|
+
...state,
|
|
4914
|
+
nextBefore: previousNextBefore,
|
|
4915
|
+
loadingOlder: false
|
|
4916
|
+
}));
|
|
4062
4917
|
}
|
|
4063
|
-
|
|
4064
|
-
}
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4918
|
+
if (options.suppressErrors === false) throw error;
|
|
4919
|
+
} finally {
|
|
4920
|
+
this.replaceInFlight -= 1;
|
|
4921
|
+
}
|
|
4922
|
+
};
|
|
4923
|
+
const run = this.replaceQueue.then(operation);
|
|
4924
|
+
this.replaceQueue = run.catch(() => void 0);
|
|
4925
|
+
return run;
|
|
4926
|
+
}
|
|
4927
|
+
invalidateOlderPaging() {
|
|
4928
|
+
this.pagingGeneration += 1;
|
|
4929
|
+
this.olderPending = null;
|
|
4930
|
+
}
|
|
4931
|
+
beginCommandRecovery() {
|
|
4932
|
+
this.commandRecoveryGeneration += 1;
|
|
4933
|
+
this.commandRestoreRetryAttempt = 0;
|
|
4934
|
+
this.bufferedCommandDeliveries ??= [];
|
|
4935
|
+
if (this.commandRestoreRetryHandle !== null) {
|
|
4936
|
+
globalThis.clearTimeout(this.commandRestoreRetryHandle);
|
|
4937
|
+
this.commandRestoreRetryHandle = null;
|
|
4068
4938
|
}
|
|
4939
|
+
return this.commandRecoveryGeneration;
|
|
4069
4940
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4941
|
+
scheduleHistoricalCommandRestore(generation) {
|
|
4942
|
+
const restore = async () => {
|
|
4943
|
+
if (this.disposed || generation !== this.commandRecoveryGeneration) return;
|
|
4944
|
+
try {
|
|
4945
|
+
const commands = await this.runtime.fetchCommands(this.sessionId);
|
|
4946
|
+
if (this.disposed) return;
|
|
4947
|
+
for (const command of commands) {
|
|
4948
|
+
if (this.disposed) return;
|
|
4949
|
+
this.deliverCommandNow({
|
|
4950
|
+
deliveryKey: commandDeliveryKey(command.deliveryId),
|
|
4951
|
+
action: command.action,
|
|
4952
|
+
payload: command.payload,
|
|
4953
|
+
toolCallId: command.toolCallId
|
|
4954
|
+
});
|
|
4955
|
+
}
|
|
4956
|
+
if (generation !== this.commandRecoveryGeneration) return;
|
|
4957
|
+
this.commandRestoreRetryAttempt = 0;
|
|
4958
|
+
this.flushBufferedCommands(generation);
|
|
4959
|
+
} catch {
|
|
4960
|
+
if (!this.disposed && generation === this.commandRecoveryGeneration && this.commandRestoreRetryHandle === null) {
|
|
4961
|
+
const retryDelay = Math.min(
|
|
4962
|
+
COMMAND_RESTORE_RETRY_BASE_MS * 2 ** this.commandRestoreRetryAttempt,
|
|
4963
|
+
COMMAND_RESTORE_RETRY_MAX_MS
|
|
4964
|
+
);
|
|
4965
|
+
this.commandRestoreRetryAttempt += 1;
|
|
4966
|
+
this.commandRestoreRetryHandle = globalThis.setTimeout(() => {
|
|
4967
|
+
this.commandRestoreRetryHandle = null;
|
|
4968
|
+
if (!this.disposed && generation === this.commandRecoveryGeneration) {
|
|
4969
|
+
this.scheduleHistoricalCommandRestore(generation);
|
|
4970
|
+
}
|
|
4971
|
+
}, retryDelay);
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
};
|
|
4975
|
+
const run = this.commandRestoreQueue.then(restore);
|
|
4976
|
+
this.commandRestoreQueue = run.catch(() => void 0);
|
|
4977
|
+
}
|
|
4978
|
+
flushBufferedCommands(generation) {
|
|
4979
|
+
const pending = [...this.bufferedCommandDeliveries ?? []];
|
|
4980
|
+
this.bufferedCommandDeliveries = [];
|
|
4981
|
+
let index = 0;
|
|
4982
|
+
while (index < pending.length) {
|
|
4983
|
+
if (this.disposed) return;
|
|
4984
|
+
if (generation !== this.commandRecoveryGeneration) {
|
|
4985
|
+
const currentGenerationBuffer = this.bufferedCommandDeliveries ?? [];
|
|
4986
|
+
this.bufferedCommandDeliveries = [
|
|
4987
|
+
...pending.slice(index),
|
|
4988
|
+
...currentGenerationBuffer
|
|
4989
|
+
];
|
|
4990
|
+
return;
|
|
4991
|
+
}
|
|
4992
|
+
this.deliverCommandNow(pending[index]);
|
|
4993
|
+
index += 1;
|
|
4994
|
+
const reentrantBuffer = this.bufferedCommandDeliveries;
|
|
4995
|
+
if (index === pending.length && reentrantBuffer && reentrantBuffer.length > 0) {
|
|
4996
|
+
pending.push(...reentrantBuffer);
|
|
4997
|
+
this.bufferedCommandDeliveries = [];
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
if (!this.disposed && generation === this.commandRecoveryGeneration) {
|
|
5001
|
+
this.bufferedCommandDeliveries = null;
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
/** 首次订阅前的 REST 历史建立语义事件基线,随后同一工具的订阅回放不再投递。 */
|
|
5005
|
+
seedToolCallDeliveryBaseline(turns) {
|
|
4080
5006
|
for (const turn of turns) {
|
|
4081
|
-
for (const
|
|
4082
|
-
|
|
4083
|
-
|
|
5007
|
+
for (const toolCall of turn.tool_calls) {
|
|
5008
|
+
this.seenToolCallIds.add(toolCall.id);
|
|
5009
|
+
if (toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled") {
|
|
5010
|
+
this.finishedToolCallIds.add(toolCall.id);
|
|
4084
5011
|
}
|
|
4085
5012
|
}
|
|
4086
5013
|
}
|
|
4087
5014
|
}
|
|
4088
|
-
ensureJoined() {
|
|
5015
|
+
ensureJoined(liveRevisionBoundary = this.liveRevision) {
|
|
4089
5016
|
if (this.joined) return Promise.resolve();
|
|
4090
5017
|
if (this.joinPending) return this.joinPending;
|
|
4091
5018
|
const join = this.runtime.emitWithAck(
|
|
@@ -4093,8 +5020,7 @@ var AgentSession = class _AgentSession {
|
|
|
4093
5020
|
{
|
|
4094
5021
|
session_id: this.sessionId,
|
|
4095
5022
|
event_format: "raw",
|
|
4096
|
-
resume: true
|
|
4097
|
-
stream_tokens: this.runtime.streamTokens
|
|
5023
|
+
resume: true
|
|
4098
5024
|
},
|
|
4099
5025
|
5e3
|
|
4100
5026
|
).then((response) => {
|
|
@@ -4104,7 +5030,7 @@ var AgentSession = class _AgentSession {
|
|
|
4104
5030
|
);
|
|
4105
5031
|
}
|
|
4106
5032
|
this.joined = true;
|
|
4107
|
-
this._applyRunningState(Boolean(response?.is_running));
|
|
5033
|
+
this._applyRunningState(Boolean(response?.is_running), liveRevisionBoundary);
|
|
4108
5034
|
}).finally(() => {
|
|
4109
5035
|
this.joinPending = null;
|
|
4110
5036
|
});
|
|
@@ -4167,16 +5093,28 @@ var SessionHub = class {
|
|
|
4167
5093
|
* 只有最后一份归还才退订房间——否则 StrictMode 双跑、路由抖动这类"连两次
|
|
4168
5094
|
* 只释放一次"的场景会把仍在用的会话退订掉,之后所有 turn/chat 事件静默丢失。
|
|
4169
5095
|
*/
|
|
4170
|
-
async connect(sessionId) {
|
|
5096
|
+
async connect(sessionId, options = {}) {
|
|
4171
5097
|
const existing = this.sessions.get(sessionId);
|
|
4172
5098
|
if (existing && !existing.isDisposed) {
|
|
4173
5099
|
this.hold(sessionId);
|
|
5100
|
+
try {
|
|
5101
|
+
options.setup?.(existing);
|
|
5102
|
+
} catch (error) {
|
|
5103
|
+
existing.dispose();
|
|
5104
|
+
throw error;
|
|
5105
|
+
}
|
|
4174
5106
|
return this.bootstrapPending.get(sessionId) ?? existing;
|
|
4175
5107
|
}
|
|
4176
5108
|
this.ensureBound();
|
|
4177
5109
|
const session = new AgentSession(sessionId, this.runtimeFor(), this.connection);
|
|
4178
5110
|
this.sessions.set(sessionId, session);
|
|
4179
5111
|
this.hold(sessionId);
|
|
5112
|
+
try {
|
|
5113
|
+
options.setup?.(session);
|
|
5114
|
+
} catch (error) {
|
|
5115
|
+
session.dispose();
|
|
5116
|
+
throw error;
|
|
5117
|
+
}
|
|
4180
5118
|
this.ensureConnected();
|
|
4181
5119
|
const bootstrap = session._bootstrap().then(() => session);
|
|
4182
5120
|
this.bootstrapPending.set(sessionId, bootstrap);
|
|
@@ -4231,7 +5169,6 @@ var SessionHub = class {
|
|
|
4231
5169
|
}
|
|
4232
5170
|
runtimeFor() {
|
|
4233
5171
|
return {
|
|
4234
|
-
streamTokens: this.client.options?.streamTokens !== false,
|
|
4235
5172
|
emitEvent: (event, data) => {
|
|
4236
5173
|
;
|
|
4237
5174
|
this.client.socket().emit(event, data);
|
|
@@ -4247,7 +5184,8 @@ var SessionHub = class {
|
|
|
4247
5184
|
});
|
|
4248
5185
|
}),
|
|
4249
5186
|
ensureConnected: () => this.ensureConnected(),
|
|
4250
|
-
|
|
5187
|
+
fetchTurnsPage: (sessionId, before) => this.client.sessions.getSessionTurnsPage(sessionId, { before }),
|
|
5188
|
+
fetchCommands: (sessionId) => this.client.sessions.getSessionTurnCommands(sessionId),
|
|
4251
5189
|
fetchSessionInfo: (sessionId) => this.client.sessions.getSession(sessionId),
|
|
4252
5190
|
updateReplay: (sessionId, payload) => this.client.sessions.updateReplaySession(sessionId, payload).then((result) => result.replay_state ?? null),
|
|
4253
5191
|
listDir: (sessionId, dirPath) => this.client.sessions.listDir(sessionId, dirPath),
|
|
@@ -4339,9 +5277,25 @@ var SessionHub = class {
|
|
|
4339
5277
|
socket.on("turn:end", (data) => {
|
|
4340
5278
|
for (const session of this.route(data.session_id)) session._handleTurnEnd(data);
|
|
4341
5279
|
});
|
|
4342
|
-
socket.on(
|
|
4343
|
-
|
|
4344
|
-
|
|
5280
|
+
socket.on(
|
|
5281
|
+
"turn:timing",
|
|
5282
|
+
(data) => {
|
|
5283
|
+
for (const session of this.route(data.session_id)) {
|
|
5284
|
+
session._handleTurnEvents([
|
|
5285
|
+
{ type: "llm:first_token:delivered", payload: data }
|
|
5286
|
+
]);
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
);
|
|
5290
|
+
socket.on(
|
|
5291
|
+
"turn:events",
|
|
5292
|
+
(data, acknowledge) => {
|
|
5293
|
+
acknowledgeTurnEvents(acknowledge);
|
|
5294
|
+
for (const session of this.route(data.session_id)) {
|
|
5295
|
+
session._handleTurnEvents(data.events ?? [], data.replayed === true);
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
);
|
|
4345
5299
|
socket.on("chat:start", (data) => {
|
|
4346
5300
|
for (const session of this.route(data?.session_id)) session._handleChatStart();
|
|
4347
5301
|
});
|
|
@@ -4469,21 +5423,14 @@ function resolveAuthToken(options) {
|
|
|
4469
5423
|
return token ? token : null;
|
|
4470
5424
|
}
|
|
4471
5425
|
|
|
4472
|
-
// src/version.ts
|
|
4473
|
-
var SDK_NAME = "agent-client";
|
|
4474
|
-
var SDK_VERSION = true ? "2610.0.0-beta.4" : "1.1.1";
|
|
4475
|
-
|
|
4476
5426
|
// src/socket.ts
|
|
4477
|
-
function withSdkIdentity(auth) {
|
|
4478
|
-
return { ...auth ?? {}, sdk: SDK_NAME, sdk_version: SDK_VERSION };
|
|
4479
|
-
}
|
|
4480
5427
|
function createSocket(options) {
|
|
4481
5428
|
const token = resolveAuthToken(options);
|
|
4482
5429
|
return io(options.baseUrl, {
|
|
4483
5430
|
path: options.path ?? "/socket.io",
|
|
4484
5431
|
withCredentials: true,
|
|
4485
5432
|
query: token ? { token } : void 0,
|
|
4486
|
-
auth: typeof options.token === "function" ? (cb) => cb(
|
|
5433
|
+
auth: typeof options.token === "function" ? (cb) => cb(buildSocketAuth(options) ?? {}) : buildSocketAuth(options) ?? {},
|
|
4487
5434
|
autoConnect: false
|
|
4488
5435
|
});
|
|
4489
5436
|
}
|
|
@@ -4499,6 +5446,7 @@ var BladeClient = class {
|
|
|
4499
5446
|
options;
|
|
4500
5447
|
auth;
|
|
4501
5448
|
headless;
|
|
5449
|
+
computers;
|
|
4502
5450
|
models;
|
|
4503
5451
|
sessions;
|
|
4504
5452
|
/** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
|
|
@@ -4510,6 +5458,7 @@ var BladeClient = class {
|
|
|
4510
5458
|
};
|
|
4511
5459
|
this.auth = new AuthResource(this);
|
|
4512
5460
|
this.headless = new HeadlessResource(this);
|
|
5461
|
+
this.computers = new ComputersResource(this);
|
|
4513
5462
|
this.models = new ModelsResource(this);
|
|
4514
5463
|
this.sessions = new SessionsResource(this);
|
|
4515
5464
|
this.hub = new SessionHub(this);
|
|
@@ -4864,6 +5813,90 @@ function parseXhrHeaders(rawHeaders) {
|
|
|
4864
5813
|
return headers;
|
|
4865
5814
|
}
|
|
4866
5815
|
|
|
5816
|
+
// src/platform-endpoints.ts
|
|
5817
|
+
var PLATFORM_SERVICE_NAMES = [
|
|
5818
|
+
"os",
|
|
5819
|
+
"hub",
|
|
5820
|
+
"agent",
|
|
5821
|
+
"oauth",
|
|
5822
|
+
"llmGateway",
|
|
5823
|
+
"gitea",
|
|
5824
|
+
"tile"
|
|
5825
|
+
];
|
|
5826
|
+
var EMPTY_PLATFORM_ENDPOINTS = Object.freeze({
|
|
5827
|
+
services: Object.freeze({})
|
|
5828
|
+
});
|
|
5829
|
+
function defaultDocumentBaseUrl() {
|
|
5830
|
+
if (typeof document === "undefined") return void 0;
|
|
5831
|
+
if (document.querySelector("base[href]")) return document.baseURI;
|
|
5832
|
+
if (typeof location !== "undefined" && location.origin) return `${location.origin}/`;
|
|
5833
|
+
return document.baseURI;
|
|
5834
|
+
}
|
|
5835
|
+
function normalizeServiceUrl(value) {
|
|
5836
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
5837
|
+
if (value.includes("?") || value.includes("#")) return null;
|
|
5838
|
+
try {
|
|
5839
|
+
const url = new URL(value.trim());
|
|
5840
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
5841
|
+
if (url.username || url.password || url.search || url.hash) return null;
|
|
5842
|
+
return url.toString().replace(/\/$/, "");
|
|
5843
|
+
} catch {
|
|
5844
|
+
return null;
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
function parsePlatformEndpoints(value) {
|
|
5848
|
+
if (!isRecord(value) || !isRecord(value.services)) return null;
|
|
5849
|
+
const services = {};
|
|
5850
|
+
for (const name of PLATFORM_SERVICE_NAMES) {
|
|
5851
|
+
if (!(name in value.services)) continue;
|
|
5852
|
+
const url = normalizeServiceUrl(value.services[name]);
|
|
5853
|
+
if (!url) return null;
|
|
5854
|
+
if (name === "agent" && new URL(url).pathname !== "/") return null;
|
|
5855
|
+
services[name] = url;
|
|
5856
|
+
}
|
|
5857
|
+
return { services };
|
|
5858
|
+
}
|
|
5859
|
+
async function loadPlatformEndpoints(options = {}) {
|
|
5860
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
5861
|
+
const baseUrl = options.baseUrl ?? defaultDocumentBaseUrl();
|
|
5862
|
+
if (!fetchImpl || !baseUrl) return EMPTY_PLATFORM_ENDPOINTS;
|
|
5863
|
+
const controller = new AbortController();
|
|
5864
|
+
const timeout = globalThis.setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
|
|
5865
|
+
try {
|
|
5866
|
+
const response = await fetchImpl(new URL("config.json", baseUrl), {
|
|
5867
|
+
cache: "no-store",
|
|
5868
|
+
credentials: "same-origin",
|
|
5869
|
+
headers: { Accept: "application/json" },
|
|
5870
|
+
signal: controller.signal
|
|
5871
|
+
});
|
|
5872
|
+
if (!response.ok) return EMPTY_PLATFORM_ENDPOINTS;
|
|
5873
|
+
return parsePlatformEndpoints(await response.json()) ?? EMPTY_PLATFORM_ENDPOINTS;
|
|
5874
|
+
} catch {
|
|
5875
|
+
return EMPTY_PLATFORM_ENDPOINTS;
|
|
5876
|
+
} finally {
|
|
5877
|
+
globalThis.clearTimeout(timeout);
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
function resolveServiceUrl(endpoints, name, path = "") {
|
|
5881
|
+
const baseUrl = endpoints.services[name];
|
|
5882
|
+
if (!baseUrl) return null;
|
|
5883
|
+
if (!path) return baseUrl;
|
|
5884
|
+
const pathReference = path.trimStart();
|
|
5885
|
+
if (path.includes("\\") || pathReference.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(pathReference)) {
|
|
5886
|
+
return null;
|
|
5887
|
+
}
|
|
5888
|
+
const base = new URL(`${baseUrl}/`);
|
|
5889
|
+
const target = new URL(path.replace(/^\/+/, ""), base);
|
|
5890
|
+
if (target.origin !== base.origin || !target.pathname.startsWith(base.pathname)) {
|
|
5891
|
+
return null;
|
|
5892
|
+
}
|
|
5893
|
+
return target.toString();
|
|
5894
|
+
}
|
|
5895
|
+
|
|
5896
|
+
// src/version.ts
|
|
5897
|
+
var SDK_NAME = "agent-client";
|
|
5898
|
+
var SDK_VERSION = true ? "2610.0.0-beta.40" : "1.1.1";
|
|
5899
|
+
|
|
4867
5900
|
// src/commands/protocol.ts
|
|
4868
5901
|
function isCommandEnvelope(value) {
|
|
4869
5902
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -4967,7 +6000,9 @@ export {
|
|
|
4967
6000
|
BladeApiError,
|
|
4968
6001
|
BladeClient,
|
|
4969
6002
|
ClientProjectionBuilder,
|
|
6003
|
+
ComputersResource,
|
|
4970
6004
|
DEFAULT_REPLAY_SPEED,
|
|
6005
|
+
EMPTY_PLATFORM_ENDPOINTS,
|
|
4971
6006
|
LayoutType,
|
|
4972
6007
|
ModelsResource,
|
|
4973
6008
|
SDK_NAME,
|
|
@@ -4979,21 +6014,40 @@ export {
|
|
|
4979
6014
|
Task,
|
|
4980
6015
|
TaskStatus,
|
|
4981
6016
|
acceptedPostChatFollowupCompletesLatestRun,
|
|
6017
|
+
acknowledgeTurnEvents,
|
|
4982
6018
|
buildMessageContent,
|
|
6019
|
+
canToggleComputer,
|
|
6020
|
+
chatErrorForDisplay,
|
|
6021
|
+
computerDaemonVersion,
|
|
6022
|
+
computerOS,
|
|
6023
|
+
computerPlatformLabel,
|
|
6024
|
+
computerState,
|
|
4983
6025
|
connectEmbedded,
|
|
4984
6026
|
contentPreview,
|
|
6027
|
+
contextProjectionData,
|
|
4985
6028
|
createInitialSessionState,
|
|
4986
6029
|
createSocket,
|
|
4987
6030
|
extractTextAttachments,
|
|
6031
|
+
getContextDisplayState,
|
|
6032
|
+
getContextGroupDisplayState,
|
|
4988
6033
|
getFileParts,
|
|
4989
6034
|
getImageParts,
|
|
4990
6035
|
getTextContent,
|
|
6036
|
+
groupAdjacentContextRuns,
|
|
4991
6037
|
groupMessagesByLoop,
|
|
6038
|
+
hasChatRunEvent,
|
|
4992
6039
|
isCommandEnvelope,
|
|
4993
6040
|
isHiddenInternalMessage,
|
|
4994
6041
|
isInboundEnvelope,
|
|
4995
6042
|
latestPostChatFollowup,
|
|
6043
|
+
loadPlatformEndpoints,
|
|
4996
6044
|
normalizeMessageContent,
|
|
6045
|
+
prependOlder,
|
|
6046
|
+
reconcileHistoricalUserTurns,
|
|
6047
|
+
reconcileOptimisticUserTurns,
|
|
6048
|
+
replaceWindow,
|
|
6049
|
+
resolveServiceUrl,
|
|
6050
|
+
sortComputers,
|
|
4997
6051
|
toReplaySnapshot,
|
|
4998
6052
|
transformSlashCommand
|
|
4999
6053
|
};
|