@blade-hq/agent-client 2608.0.6 → 2608.0.7-beta.0
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 +137 -5
- package/dist/blade-client.d.ts +2 -0
- package/dist/index.d.ts +10 -8
- package/dist/index.js +327 -72
- package/dist/index.js.map +1 -1
- package/dist/resources/auth.d.ts +26 -0
- package/dist/resources/models.d.ts +22 -0
- package/dist/resources/sessions.d.ts +45 -3
- package/dist/schemas/message-utils.d.ts +11 -1
- package/dist/schemas/message.d.ts +2 -0
- package/dist/schemas/projection-utils.d.ts +3 -0
- package/dist/schemas/projection.d.ts +16 -0
- package/dist/schemas/session.d.ts +18 -1
- package/dist/session/agent-session.d.ts +23 -1
- package/dist/session/state.d.ts +15 -1
- package/dist/shared/projection/builder.d.ts +5 -0
- package/dist/shared/projection/helpers.d.ts +10 -0
- package/dist/shared/projection/index.d.ts +1 -1
- package/dist/types/socket-events.d.ts +6 -0
- package/package.json +1 -1
- package/public-api.md +187 -4
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
function isAuthCallbackMessage(value) {
|
|
3
3
|
return typeof value === "object" && value !== null && value.type === "blade-agent:sdk-auth";
|
|
4
4
|
}
|
|
5
|
+
var AUTH_ERROR_HINTS = {
|
|
6
|
+
pat_unavailable: "\u6CA1\u80FD\u4E3A\u4F60\u51C6\u5907\u8BBF\u95EE\u51ED\u636E\u3002\u8BF7\u9000\u51FA\u540E\u91CD\u65B0\u767B\u5F55 Blade \u518D\u8BD5\u4E00\u6B21\uFF1B\u82E5\u4ECD\u7136\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002"
|
|
7
|
+
};
|
|
5
8
|
function randomState() {
|
|
6
9
|
const bytes = new Uint8Array(24);
|
|
7
10
|
crypto.getRandomValues(bytes);
|
|
@@ -79,18 +82,22 @@ async function loginWithPopup(client, options = {}) {
|
|
|
79
82
|
if (event.data.state !== state) return;
|
|
80
83
|
const message = event.data;
|
|
81
84
|
stopCloseWatch();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
const closePopup = () => {
|
|
86
|
+
try {
|
|
87
|
+
popup.close();
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
};
|
|
86
91
|
if (message.status === "denied") {
|
|
92
|
+
closePopup();
|
|
87
93
|
settle(() => reject(new Error("\u4F60\u53D6\u6D88\u4E86\u6388\u6743\uFF0C\u672A\u5B8C\u6210\u767B\u5F55\u3002")));
|
|
88
94
|
return;
|
|
89
95
|
}
|
|
90
96
|
if (message.status !== "success" || !message.code) {
|
|
91
|
-
settle(() => reject(new Error(message.error
|
|
97
|
+
settle(() => reject(new Error(AUTH_ERROR_HINTS[message.error ?? ""] ?? message.error ?? "\u767B\u5F55\u5931\u8D25\uFF0C\u672A\u53D6\u5F97\u6388\u6743\u7801\u3002")));
|
|
92
98
|
return;
|
|
93
99
|
}
|
|
100
|
+
closePopup();
|
|
94
101
|
client.json("POST", "/api/auth/sdk/token", {
|
|
95
102
|
code: message.code,
|
|
96
103
|
state,
|
|
@@ -174,6 +181,24 @@ var AuthResource = class {
|
|
|
174
181
|
login(options) {
|
|
175
182
|
return this.client.login(options);
|
|
176
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* 用一次性 code 换 PAT。给**服务端**用:浏览器把 code 交给你自己的后端,
|
|
186
|
+
* 后端换到的 PAT 不进浏览器。浏览器里直接用 `login()` 就够了。
|
|
187
|
+
*
|
|
188
|
+
* 服务端调用时会自己补上 `Origin` 头(浏览器会自动带,服务端不会,缺了
|
|
189
|
+
* 后端报 `missing_request_origin`)。
|
|
190
|
+
*/
|
|
191
|
+
exchangeCode(params) {
|
|
192
|
+
return this.client.jsonFromInit("/api/auth/sdk/token", {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: { Origin: params.clientOrigin },
|
|
195
|
+
body: JSON.stringify({
|
|
196
|
+
code: params.code,
|
|
197
|
+
state: params.state,
|
|
198
|
+
client_origin: params.clientOrigin
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
}
|
|
177
202
|
/** 清除 login() 存储的令牌。 */
|
|
178
203
|
logoutToken() {
|
|
179
204
|
this.client.logoutToken();
|
|
@@ -325,7 +350,11 @@ var HeadlessResource = class {
|
|
|
325
350
|
socket.on("chat:start", onChatStart);
|
|
326
351
|
socket.on("chat:end", onChatEnd);
|
|
327
352
|
socket.on("system:error", onSystemError);
|
|
328
|
-
socket.emit("session:subscribe", {
|
|
353
|
+
socket.emit("session:subscribe", {
|
|
354
|
+
session_id,
|
|
355
|
+
event_format: "raw",
|
|
356
|
+
stream_tokens: this.client.options.streamTokens !== false
|
|
357
|
+
});
|
|
329
358
|
socket.emit(
|
|
330
359
|
"chat:send",
|
|
331
360
|
{
|
|
@@ -407,6 +436,47 @@ var ModelsResource = class {
|
|
|
407
436
|
}
|
|
408
437
|
};
|
|
409
438
|
|
|
439
|
+
// src/schemas/session.ts
|
|
440
|
+
import { type } from "arktype";
|
|
441
|
+
var SessionStatus = type(
|
|
442
|
+
"'created' | 'running' | 'completed' | 'failed' | 'interrupted' | 'waiting_for_input'"
|
|
443
|
+
);
|
|
444
|
+
var DEFAULT_REPLAY_SPEED = 5;
|
|
445
|
+
var SessionInfo = type({
|
|
446
|
+
id: "string",
|
|
447
|
+
intent: "string",
|
|
448
|
+
status: SessionStatus,
|
|
449
|
+
created_at: "string",
|
|
450
|
+
updated_at: "string",
|
|
451
|
+
"shared?": "boolean",
|
|
452
|
+
"memory_enabled?": "boolean",
|
|
453
|
+
"is_persistent?": "boolean",
|
|
454
|
+
"is_headless?": "boolean",
|
|
455
|
+
"viewer_role?": "'owner' | 'viewer'",
|
|
456
|
+
"template_id?": "string | null",
|
|
457
|
+
"model?": "string | null",
|
|
458
|
+
"enable_thinking?": "boolean | null",
|
|
459
|
+
"solution_id?": "string | null",
|
|
460
|
+
"solution_ref?": "unknown",
|
|
461
|
+
"biz_role_id?": "string | null",
|
|
462
|
+
"solution?": "unknown",
|
|
463
|
+
"plan_summary?": "string | null",
|
|
464
|
+
"primary_skill_id?": "string | null",
|
|
465
|
+
// ship-attack v2:后端 engine.set_bound_skill 写入的"绑定 skill id"。
|
|
466
|
+
// 未绑定 / 非 ship-attack session 为 null。
|
|
467
|
+
"bound_skill_id?": "string | null",
|
|
468
|
+
"replay_state?": "unknown",
|
|
469
|
+
"is_pinned?": "boolean",
|
|
470
|
+
"pinned_at?": "string | null",
|
|
471
|
+
"is_example?": "boolean",
|
|
472
|
+
"ports?": "unknown",
|
|
473
|
+
"disable_tools?": "string[]",
|
|
474
|
+
"runtime_type?": "string | null",
|
|
475
|
+
"daemon_id?": "string | null",
|
|
476
|
+
"workspace_path?": "string | null",
|
|
477
|
+
"match?": "unknown"
|
|
478
|
+
});
|
|
479
|
+
|
|
410
480
|
// src/session/definition.ts
|
|
411
481
|
var SessionSetupError = class extends Error {
|
|
412
482
|
constructor(message, stage, sessionId, options) {
|
|
@@ -827,6 +897,28 @@ function sourceLoopFor(loopId, loopDescriptions) {
|
|
|
827
897
|
if (!description) return null;
|
|
828
898
|
return { loop_name: loopId, description };
|
|
829
899
|
}
|
|
900
|
+
var MAX_FINAL_ARTIFACTS = 20;
|
|
901
|
+
function normalizeFinalArtifacts(raw) {
|
|
902
|
+
if (!Array.isArray(raw)) return [];
|
|
903
|
+
const artifacts = [];
|
|
904
|
+
const seen = /* @__PURE__ */ new Set();
|
|
905
|
+
for (const item of raw) {
|
|
906
|
+
if (typeof item !== "object" || item === null) continue;
|
|
907
|
+
const record = item;
|
|
908
|
+
const target = String(record.target ?? "").trim();
|
|
909
|
+
if (!target || seen.has(target)) continue;
|
|
910
|
+
const isLink = /^https?:\/\//i.test(target);
|
|
911
|
+
if (!isLink && target.split("/").includes("..")) continue;
|
|
912
|
+
seen.add(target);
|
|
913
|
+
artifacts.push({
|
|
914
|
+
kind: isLink ? "link" : "file",
|
|
915
|
+
target,
|
|
916
|
+
label: String(record.label ?? "").trim()
|
|
917
|
+
});
|
|
918
|
+
if (artifacts.length === MAX_FINAL_ARTIFACTS) break;
|
|
919
|
+
}
|
|
920
|
+
return artifacts;
|
|
921
|
+
}
|
|
830
922
|
function normalizePostChatFollowup(data, assistantEntryId) {
|
|
831
923
|
const raw = data.suggestions;
|
|
832
924
|
const suggestions = [];
|
|
@@ -838,11 +930,17 @@ function normalizePostChatFollowup(data, assistantEntryId) {
|
|
|
838
930
|
}
|
|
839
931
|
}
|
|
840
932
|
const recaption = String(data.recaption ?? "");
|
|
841
|
-
|
|
933
|
+
const finalArtifacts = normalizeFinalArtifacts(data.final_artifacts);
|
|
934
|
+
const feedbackEligible = data.feedback_eligible === true;
|
|
935
|
+
if (suggestions.length === 0 && !recaption && finalArtifacts.length === 0 && !feedbackEligible)
|
|
936
|
+
return null;
|
|
842
937
|
return {
|
|
843
938
|
assistant_entry_id: assistantEntryId,
|
|
844
939
|
recaption,
|
|
845
|
-
suggestions: suggestions.slice(0, 3)
|
|
940
|
+
suggestions: suggestions.slice(0, 3),
|
|
941
|
+
final_artifacts: finalArtifacts,
|
|
942
|
+
feedback_eligible: feedbackEligible,
|
|
943
|
+
feedback_scope: "latest_result"
|
|
846
944
|
};
|
|
847
945
|
}
|
|
848
946
|
function normalizeChildPause(pausePayload) {
|
|
@@ -926,11 +1024,60 @@ function toolStatusFromResult(result) {
|
|
|
926
1024
|
}
|
|
927
1025
|
|
|
928
1026
|
// src/shared/projection/builder.ts
|
|
1027
|
+
function hasChatRunEvent(events) {
|
|
1028
|
+
return events.some(
|
|
1029
|
+
(event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
function acceptedPostChatFollowupCompletesLatestRun(events, updates) {
|
|
1033
|
+
const acceptedAssistantEntryIds = new Set(
|
|
1034
|
+
updates.flatMap((update) => {
|
|
1035
|
+
if (update.kind !== "upsert") return [];
|
|
1036
|
+
const followup = update.turn.post_chat_followup;
|
|
1037
|
+
return followup?.assistant_entry_id ? [followup.assistant_entry_id] : [];
|
|
1038
|
+
})
|
|
1039
|
+
);
|
|
1040
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1041
|
+
const event = events[index];
|
|
1042
|
+
if (event?.type !== "session:post_chat_followup") continue;
|
|
1043
|
+
const assistantEntryId = String(event.payload.assistant_entry_id ?? "");
|
|
1044
|
+
return acceptedAssistantEntryIds.has(assistantEntryId) && !hasChatRunEvent(events.slice(index + 1));
|
|
1045
|
+
}
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
929
1048
|
function isConversationTurn(turn) {
|
|
930
1049
|
return (turn.role === "user" || turn.role === "assistant") && (turn.kind ?? "message") === "message" && !turn.blocks.some(
|
|
931
1050
|
(block) => block.type === "mode_change" || block.type === "plan_status" || block.type === "ask_user_answer"
|
|
932
1051
|
);
|
|
933
1052
|
}
|
|
1053
|
+
function completedResponseText(content) {
|
|
1054
|
+
if (typeof content === "string") return content;
|
|
1055
|
+
if (!Array.isArray(content)) return content == null ? "" : String(content);
|
|
1056
|
+
return content.filter(
|
|
1057
|
+
(part) => typeof part === "object" && part !== null && !Array.isArray(part)
|
|
1058
|
+
).filter((part) => part.type === "text").map((part) => String(part.text ?? "")).join("");
|
|
1059
|
+
}
|
|
1060
|
+
function replaceTextContent(state, content) {
|
|
1061
|
+
if (state.blocks.some((block) => block.type === "text")) return;
|
|
1062
|
+
const replacement = { type: "text", content };
|
|
1063
|
+
const blocks = [];
|
|
1064
|
+
let inserted = false;
|
|
1065
|
+
for (const block of state.blocks) {
|
|
1066
|
+
if (block.type === "text") {
|
|
1067
|
+
if (content && !inserted) {
|
|
1068
|
+
blocks.push(replacement);
|
|
1069
|
+
inserted = true;
|
|
1070
|
+
}
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
blocks.push(block);
|
|
1074
|
+
}
|
|
1075
|
+
if (content && !inserted) {
|
|
1076
|
+
const toolIndex = blocks.findIndex((block) => block.type === "tool_use");
|
|
1077
|
+
blocks.splice(toolIndex < 0 ? blocks.length : toolIndex, 0, replacement);
|
|
1078
|
+
}
|
|
1079
|
+
state.blocks = blocks;
|
|
1080
|
+
}
|
|
934
1081
|
var ClientProjectionBuilder = class {
|
|
935
1082
|
activeTurns = /* @__PURE__ */ new Map();
|
|
936
1083
|
lastTurnIds = /* @__PURE__ */ new Map();
|
|
@@ -1013,6 +1160,9 @@ var ClientProjectionBuilder = class {
|
|
|
1013
1160
|
return this.onDelta(state, "thinking", "", String(payload.content ?? ""));
|
|
1014
1161
|
case "llm:content:delta":
|
|
1015
1162
|
return this.onDelta(state, "text", "", String(payload.content ?? ""));
|
|
1163
|
+
case "llm:content:complete":
|
|
1164
|
+
replaceTextContent(state, String(payload.content ?? ""));
|
|
1165
|
+
return this.syncPatch(state);
|
|
1016
1166
|
case "llm:tool_call:created":
|
|
1017
1167
|
return this.onToolCallCreated(state, payload);
|
|
1018
1168
|
case "llm:response:done":
|
|
@@ -1302,6 +1452,9 @@ var ClientProjectionBuilder = class {
|
|
|
1302
1452
|
if (typeof payload.usage === "object" && payload.usage !== null) {
|
|
1303
1453
|
state.usage = { ...payload.usage };
|
|
1304
1454
|
}
|
|
1455
|
+
if ("content" in payload) {
|
|
1456
|
+
replaceTextContent(state, completedResponseText(payload.content));
|
|
1457
|
+
}
|
|
1305
1458
|
const toolCalls = payload.tool_calls;
|
|
1306
1459
|
if (Array.isArray(toolCalls)) {
|
|
1307
1460
|
for (const raw of toolCalls) {
|
|
@@ -2105,6 +2258,11 @@ var SessionsResource = class {
|
|
|
2105
2258
|
listSessionsWithSkillData() {
|
|
2106
2259
|
return this.client.json("GET", "/api/sessions/with-skill-data");
|
|
2107
2260
|
}
|
|
2261
|
+
searchOwnedContent(params) {
|
|
2262
|
+
const searchParams = new URLSearchParams({ q: params.q.trim() });
|
|
2263
|
+
if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
2264
|
+
return this.client.json("GET", `/api/search?${searchParams.toString()}`);
|
|
2265
|
+
}
|
|
2108
2266
|
createSession(intent, template_id, primary_skill_id) {
|
|
2109
2267
|
return this.createSessionWithRequest({ intent, template_id, primary_skill_id });
|
|
2110
2268
|
}
|
|
@@ -2130,12 +2288,16 @@ var SessionsResource = class {
|
|
|
2130
2288
|
pinSession(sessionId, pinned) {
|
|
2131
2289
|
return this.client.json("PATCH", `/api/sessions/${sessionId}/pin`, { pinned });
|
|
2132
2290
|
}
|
|
2133
|
-
startReplaySession(sourceSessionId, speed =
|
|
2291
|
+
startReplaySession(sourceSessionId, speed = DEFAULT_REPLAY_SPEED) {
|
|
2134
2292
|
return this.client.json("POST", `/api/sessions/${sourceSessionId}/replay`, { speed });
|
|
2135
2293
|
}
|
|
2136
2294
|
updateReplaySession(sessionId, payload) {
|
|
2137
2295
|
return this.client.json("PATCH", `/api/sessions/${sessionId}/replay`, payload);
|
|
2138
2296
|
}
|
|
2297
|
+
/** 查询会话能否作为回放来源;不支持时 reason 可直接展示,用来提前禁用入口。 */
|
|
2298
|
+
getReplayPreview(sessionId) {
|
|
2299
|
+
return this.client.json("GET", `/api/sessions/${sessionId}/replay/preview`);
|
|
2300
|
+
}
|
|
2139
2301
|
updateSharing(sessionId, shared) {
|
|
2140
2302
|
return this.client.json("PATCH", `/api/sessions/${sessionId}/sharing`, { shared });
|
|
2141
2303
|
}
|
|
@@ -2167,6 +2329,16 @@ var SessionsResource = class {
|
|
|
2167
2329
|
);
|
|
2168
2330
|
return projectHistory(entries);
|
|
2169
2331
|
}
|
|
2332
|
+
listResultFeedback(sessionId) {
|
|
2333
|
+
return this.client.json("GET", `/api/sessions/${encodeURIComponent(sessionId)}/feedback`);
|
|
2334
|
+
}
|
|
2335
|
+
putResultFeedback(sessionId, assistantEntryId, feedback) {
|
|
2336
|
+
return this.client.json(
|
|
2337
|
+
"PUT",
|
|
2338
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/turns/${encodeURIComponent(assistantEntryId)}/feedback`,
|
|
2339
|
+
feedback
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2170
2342
|
getSessionContextStats(sessionId, init) {
|
|
2171
2343
|
return this.client.jsonFromInit(`/api/sessions/${sessionId}/context-stats`, init);
|
|
2172
2344
|
}
|
|
@@ -2589,10 +2761,15 @@ function contentPreview(content, maxLen = 80) {
|
|
|
2589
2761
|
const text = getTextContent(content).trim();
|
|
2590
2762
|
return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
|
|
2591
2763
|
}
|
|
2592
|
-
function transformSlashCommand(skillName, rawInput) {
|
|
2764
|
+
function transformSlashCommand(skillName, rawInput, { local = true, installed = true } = {}) {
|
|
2593
2765
|
const prompt = rawInput.replace(`<skill>${skillName}</skill>`, "").trim();
|
|
2594
|
-
|
|
2766
|
+
if (local) {
|
|
2767
|
+
return prompt ? `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1
|
|
2595
2768
|
${prompt}` : `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1`;
|
|
2769
|
+
}
|
|
2770
|
+
const steps = installed ? `\u8BF7\u5148\u8FD0\u884C blade hub skill use ${skillName} \u52A0\u8F7D\u6280\u80FD` : `\u8BE5\u6280\u80FD\u5C1A\u672A\u5B89\u88C5\u3002\u8BF7\u5148\u8FD0\u884C blade hub skill install ${skillName} \u5B89\u88C5\uFF0C\u518D\u8FD0\u884C blade hub skill use ${skillName} \u52A0\u8F7D`;
|
|
2771
|
+
return prompt ? `${steps}\uFF0C\u7136\u540E\u5B8C\u6210\u4EFB\u52A1\uFF1A
|
|
2772
|
+
${prompt}` : `${steps}\uFF0C\u7136\u540E\u4F7F\u7528\u5B83\u5B8C\u6210\u4EFB\u52A1`;
|
|
2596
2773
|
}
|
|
2597
2774
|
var ATTACHMENT_TAG_RE = /\[附件:\s*([^\]]+)\](?:[ \t]*已上传到工作区路径:[ \t]*([^\r\n]+))?/g;
|
|
2598
2775
|
var CONTEXT_PART_RE = /^\[上下文:\s*([^\]]+)\]\n([\s\S]*)$/;
|
|
@@ -2678,6 +2855,14 @@ function groupMessagesByLoop(messages) {
|
|
|
2678
2855
|
}
|
|
2679
2856
|
|
|
2680
2857
|
// src/session/state.ts
|
|
2858
|
+
function toReplaySnapshot(raw) {
|
|
2859
|
+
if (!raw || !raw.status) return null;
|
|
2860
|
+
return {
|
|
2861
|
+
isReplay: raw.status === "replay",
|
|
2862
|
+
speed: raw.speed ?? 1,
|
|
2863
|
+
sourceSessionId: raw.source_session_id ?? null
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2681
2866
|
function createInitialSessionState(sessionId) {
|
|
2682
2867
|
return {
|
|
2683
2868
|
sessionId,
|
|
@@ -2690,7 +2875,9 @@ function createInitialSessionState(sessionId) {
|
|
|
2690
2875
|
errorMessage: null,
|
|
2691
2876
|
askAnswers: {},
|
|
2692
2877
|
agentLoops: {},
|
|
2693
|
-
activeCompaction: null
|
|
2878
|
+
activeCompaction: null,
|
|
2879
|
+
replay: null,
|
|
2880
|
+
viewerRole: null
|
|
2694
2881
|
};
|
|
2695
2882
|
}
|
|
2696
2883
|
function parseAgentDescription(argumentsJson) {
|
|
@@ -2953,6 +3140,25 @@ function orderTurns(turns) {
|
|
|
2953
3140
|
}
|
|
2954
3141
|
return turns;
|
|
2955
3142
|
}
|
|
3143
|
+
function reconcileOptimisticUserTurns(currentTurns, incomingTurns) {
|
|
3144
|
+
const currentById = new Map(currentTurns.map((turn) => [turn.turn_id, turn]));
|
|
3145
|
+
const optimisticUserTurn = currentTurns.find(
|
|
3146
|
+
(turn) => turn.role === "user" && turn.turn_id.startsWith("local-user-")
|
|
3147
|
+
);
|
|
3148
|
+
let optimisticUserMatched = false;
|
|
3149
|
+
return incomingTurns.map((turn) => {
|
|
3150
|
+
const current = currentById.get(turn.turn_id);
|
|
3151
|
+
if (current) {
|
|
3152
|
+
const currentRenderId = current.client_render_id;
|
|
3153
|
+
return turn.client_render_id || !currentRenderId ? turn : { ...turn, client_render_id: currentRenderId };
|
|
3154
|
+
}
|
|
3155
|
+
if (!optimisticUserMatched && optimisticUserTurn && turn.role === "user" && !turn.turn_id.startsWith("local-user-")) {
|
|
3156
|
+
optimisticUserMatched = true;
|
|
3157
|
+
return { ...turn, client_render_id: optimisticUserTurn.turn_id };
|
|
3158
|
+
}
|
|
3159
|
+
return turn;
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
2956
3162
|
function areAgentLoopsEqual(left, right) {
|
|
2957
3163
|
if (!left) return Object.keys(right).length === 0;
|
|
2958
3164
|
const leftEntries = Object.entries(left);
|
|
@@ -2964,7 +3170,7 @@ function areAgentLoopsEqual(left, right) {
|
|
|
2964
3170
|
});
|
|
2965
3171
|
}
|
|
2966
3172
|
function withTurns(state, turns) {
|
|
2967
|
-
const orderedTurns = orderTurns(turns);
|
|
3173
|
+
const orderedTurns = orderTurns(reconcileOptimisticUserTurns(state.turns, turns));
|
|
2968
3174
|
const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
|
|
2969
3175
|
const isWaitingForInput = orderedTurns.some(
|
|
2970
3176
|
(turn) => turn.tool_calls.some((toolCall) => toolCall.status === "awaiting_answer")
|
|
@@ -2974,7 +3180,13 @@ function withTurns(state, turns) {
|
|
|
2974
3180
|
const preservedErrors = lastTurnId ? state.messages.filter(
|
|
2975
3181
|
(m) => m.role === "error" && typeof m.entry_id === "string" && m.entry_id.startsWith(`${ERROR_ANCHOR_PREFIX}${lastTurnId}:`)
|
|
2976
3182
|
) : [];
|
|
2977
|
-
const
|
|
3183
|
+
const turnById = new Map(orderedTurns.map((turn) => [turn.turn_id, turn]));
|
|
3184
|
+
const messagesWithRenderIds = messages.map((message) => {
|
|
3185
|
+
if (!message.entry_id) return message;
|
|
3186
|
+
const renderId = turnById.get(message.entry_id)?.client_render_id;
|
|
3187
|
+
return renderId ? { ...message, render_id: renderId } : message;
|
|
3188
|
+
});
|
|
3189
|
+
const mergedMessages = preservedErrors.length > 0 ? [...messagesWithRenderIds, ...preservedErrors] : messagesWithRenderIds;
|
|
2978
3190
|
const latestMode = [...orderedTurns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
|
|
2979
3191
|
return {
|
|
2980
3192
|
...state,
|
|
@@ -3009,14 +3221,17 @@ function addUserMessage(state, content) {
|
|
|
3009
3221
|
}
|
|
3010
3222
|
function upsertTurn(state, turn) {
|
|
3011
3223
|
let existing = [...state.turns];
|
|
3012
|
-
|
|
3013
|
-
|
|
3224
|
+
const [nextTurn] = reconcileOptimisticUserTurns(existing, [turn]);
|
|
3225
|
+
if (nextTurn.client_render_id && turn.role === "user") {
|
|
3226
|
+
existing = existing.filter(
|
|
3227
|
+
(item) => !(item.role === "user" && item.turn_id.startsWith("local-user-"))
|
|
3228
|
+
);
|
|
3014
3229
|
}
|
|
3015
|
-
const index = existing.findIndex((item) => item.turn_id ===
|
|
3230
|
+
const index = existing.findIndex((item) => item.turn_id === nextTurn.turn_id);
|
|
3016
3231
|
if (index >= 0) {
|
|
3017
|
-
existing[index] =
|
|
3232
|
+
existing[index] = nextTurn;
|
|
3018
3233
|
} else {
|
|
3019
|
-
existing.push(
|
|
3234
|
+
existing.push(nextTurn);
|
|
3020
3235
|
}
|
|
3021
3236
|
return withTurns(state, existing);
|
|
3022
3237
|
}
|
|
@@ -3120,7 +3335,7 @@ function extractTerminalChatEndStatus(events) {
|
|
|
3120
3335
|
}
|
|
3121
3336
|
return null;
|
|
3122
3337
|
}
|
|
3123
|
-
function
|
|
3338
|
+
function hasChatRunEvent2(events) {
|
|
3124
3339
|
return events.some(
|
|
3125
3340
|
(event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
3126
3341
|
);
|
|
@@ -3141,6 +3356,11 @@ var AgentSession = class _AgentSession {
|
|
|
3141
3356
|
patchFlushCancel = null;
|
|
3142
3357
|
joined = false;
|
|
3143
3358
|
joinPending = null;
|
|
3359
|
+
// 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
|
|
3360
|
+
// 请求响应可能乱序回来,晚到的旧值会把已经退出的会话又显示成回放中;用
|
|
3361
|
+
// 序号丢弃过期响应又会在失败时留下中间态。串行执行就没有这些情况:每个
|
|
3362
|
+
// 响应都是当时最新的,失败也只影响自己。
|
|
3363
|
+
replayQueue = Promise.resolve();
|
|
3144
3364
|
pendingReplayMessage = null;
|
|
3145
3365
|
pendingReplayMode;
|
|
3146
3366
|
recentChatEndAt = 0;
|
|
@@ -3167,10 +3387,15 @@ var AgentSession = class _AgentSession {
|
|
|
3167
3387
|
const session = new _AgentSession(
|
|
3168
3388
|
sessionId,
|
|
3169
3389
|
{
|
|
3390
|
+
streamTokens: false,
|
|
3170
3391
|
emitEvent: readonlyError,
|
|
3171
3392
|
emitWithAck: async () => readonlyError(),
|
|
3172
3393
|
ensureConnected: readonlyError,
|
|
3173
3394
|
fetchTurns: async () => turns,
|
|
3395
|
+
// 静态历史没有回放身份可言,给个空详情即可,不要抛错——_bootstrap
|
|
3396
|
+
// 不会跑到这里,但保持只读语义一致。
|
|
3397
|
+
fetchSessionInfo: async () => ({ replay_state: null }),
|
|
3398
|
+
updateReplay: async () => readonlyError(),
|
|
3174
3399
|
listDir: async () => readonlyError(),
|
|
3175
3400
|
countFiles: async () => readonlyError(),
|
|
3176
3401
|
downloadFile: async () => readonlyError(),
|
|
@@ -3462,10 +3687,13 @@ var AgentSession = class _AgentSession {
|
|
|
3462
3687
|
this.joinPending = null;
|
|
3463
3688
|
try {
|
|
3464
3689
|
await this.ensureJoined();
|
|
3465
|
-
await
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3690
|
+
await Promise.all([
|
|
3691
|
+
this.syncTurnsFromHistory({
|
|
3692
|
+
clearOnEmpty: true,
|
|
3693
|
+
preserveChangesSince: turnsBeforeRejoin
|
|
3694
|
+
}),
|
|
3695
|
+
this.syncReplayFromSession()
|
|
3696
|
+
]);
|
|
3469
3697
|
} catch {
|
|
3470
3698
|
}
|
|
3471
3699
|
}
|
|
@@ -3473,6 +3701,40 @@ var AgentSession = class _AgentSession {
|
|
|
3473
3701
|
async _bootstrap() {
|
|
3474
3702
|
await this.syncTurnsFromHistory({ suppressErrors: false });
|
|
3475
3703
|
await this.ensureJoined();
|
|
3704
|
+
await this.syncReplayFromSession();
|
|
3705
|
+
}
|
|
3706
|
+
/** 改回放是 owner 专属(服务端 PATCH 走 _get_owned_session)。 */
|
|
3707
|
+
get canControlReplay() {
|
|
3708
|
+
return this.state.viewerRole !== "viewer";
|
|
3709
|
+
}
|
|
3710
|
+
/** 拉一次会话详情,填回放状态和只读身份;失败按非回放会话处理,不挡启动。 */
|
|
3711
|
+
async syncReplayFromSession() {
|
|
3712
|
+
try {
|
|
3713
|
+
const info = await this.runtime.fetchSessionInfo(this.sessionId);
|
|
3714
|
+
const replay = toReplaySnapshot(info.replay_state);
|
|
3715
|
+
const viewerRole = info.viewer_role ?? null;
|
|
3716
|
+
this.update((s) => ({ ...s, replay, viewerRole }));
|
|
3717
|
+
} catch {
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
/** 调整回放倍速;不是回放会话、或只读身份时什么都不做。 */
|
|
3721
|
+
async setReplaySpeed(speed) {
|
|
3722
|
+
if (!this.state.replay?.isReplay || !this.canControlReplay) return;
|
|
3723
|
+
await this.applyReplayControl({ speed });
|
|
3724
|
+
}
|
|
3725
|
+
/** 退出回放,之后的对话交给真实模型运行;不是回放会话、或只读身份时什么都不做。 */
|
|
3726
|
+
async exitReplay() {
|
|
3727
|
+
if (!this.state.replay?.isReplay || !this.canControlReplay) return;
|
|
3728
|
+
await this.applyReplayControl({ status: "autonomous" });
|
|
3729
|
+
}
|
|
3730
|
+
/** 把一次回放控制请求排进队列;前一个完成后才发出,响应直接安装。 */
|
|
3731
|
+
applyReplayControl(payload) {
|
|
3732
|
+
const run = this.replayQueue.then(async () => {
|
|
3733
|
+
const result = await this.runtime.updateReplay(this.sessionId, payload);
|
|
3734
|
+
this.update((s) => ({ ...s, replay: toReplaySnapshot(result) }));
|
|
3735
|
+
});
|
|
3736
|
+
this.replayQueue = run.catch(() => void 0);
|
|
3737
|
+
return run;
|
|
3476
3738
|
}
|
|
3477
3739
|
/** @internal */
|
|
3478
3740
|
_handleTurnStart(turn) {
|
|
@@ -3499,7 +3761,7 @@ var AgentSession = class _AgentSession {
|
|
|
3499
3761
|
_handleTurnEvents(events) {
|
|
3500
3762
|
if (!events.length) return;
|
|
3501
3763
|
const terminalChatEndStatus = extractTerminalChatEndStatus(events);
|
|
3502
|
-
if (!terminalChatEndStatus &&
|
|
3764
|
+
if (!terminalChatEndStatus && hasChatRunEvent2(events)) {
|
|
3503
3765
|
this.markRunningIfIdle();
|
|
3504
3766
|
}
|
|
3505
3767
|
if (this.state.turns.length) {
|
|
@@ -3513,8 +3775,8 @@ var AgentSession = class _AgentSession {
|
|
|
3513
3775
|
this.emitter.emit("workspaceChanged", {});
|
|
3514
3776
|
}
|
|
3515
3777
|
}
|
|
3516
|
-
if (terminalChatEndStatus) {
|
|
3517
|
-
this.
|
|
3778
|
+
if (terminalChatEndStatus || acceptedPostChatFollowupCompletesLatestRun(events, updates)) {
|
|
3779
|
+
this.completeChat(terminalChatEndStatus ?? "completed");
|
|
3518
3780
|
}
|
|
3519
3781
|
}
|
|
3520
3782
|
/** @internal */
|
|
@@ -3524,10 +3786,7 @@ var AgentSession = class _AgentSession {
|
|
|
3524
3786
|
/** @internal */
|
|
3525
3787
|
_handleChatEnd(status) {
|
|
3526
3788
|
this.flushPatches();
|
|
3527
|
-
this.
|
|
3528
|
-
this.recentChatEndAt = Date.now();
|
|
3529
|
-
void this.syncTurnsFromHistory();
|
|
3530
|
-
this.emitter.emit("chatEnd", { status });
|
|
3789
|
+
this.completeChat(status);
|
|
3531
3790
|
}
|
|
3532
3791
|
/** @internal */
|
|
3533
3792
|
_handleSystemError(message) {
|
|
@@ -3552,6 +3811,10 @@ var AgentSession = class _AgentSession {
|
|
|
3552
3811
|
if (data.status) {
|
|
3553
3812
|
this.update((s) => ({ ...s, status: data.status ?? s.status }));
|
|
3554
3813
|
}
|
|
3814
|
+
if (data.replay_state !== void 0) {
|
|
3815
|
+
const replay = toReplaySnapshot(data.replay_state);
|
|
3816
|
+
this.update((s) => ({ ...s, replay }));
|
|
3817
|
+
}
|
|
3555
3818
|
this.emitter.emit("sessionUpdated", {
|
|
3556
3819
|
intent: data.intent,
|
|
3557
3820
|
status: data.status,
|
|
@@ -3667,6 +3930,12 @@ var AgentSession = class _AgentSession {
|
|
|
3667
3930
|
return next;
|
|
3668
3931
|
});
|
|
3669
3932
|
}
|
|
3933
|
+
completeChat(status) {
|
|
3934
|
+
this.finishStreaming(status);
|
|
3935
|
+
this.recentChatEndAt = Date.now();
|
|
3936
|
+
void this.syncTurnsFromHistory();
|
|
3937
|
+
this.emitter.emit("chatEnd", { status });
|
|
3938
|
+
}
|
|
3670
3939
|
applyTurn(turn) {
|
|
3671
3940
|
this.flushPatches();
|
|
3672
3941
|
const previous = this.state;
|
|
@@ -3849,7 +4118,12 @@ var AgentSession = class _AgentSession {
|
|
|
3849
4118
|
if (this.joinPending) return this.joinPending;
|
|
3850
4119
|
const join = this.runtime.emitWithAck(
|
|
3851
4120
|
"session:subscribe",
|
|
3852
|
-
{
|
|
4121
|
+
{
|
|
4122
|
+
session_id: this.sessionId,
|
|
4123
|
+
event_format: "raw",
|
|
4124
|
+
resume: true,
|
|
4125
|
+
stream_tokens: this.runtime.streamTokens
|
|
4126
|
+
},
|
|
3853
4127
|
5e3
|
|
3854
4128
|
).then((response) => {
|
|
3855
4129
|
if (response && response.ok === false) {
|
|
@@ -3985,6 +4259,7 @@ var SessionHub = class {
|
|
|
3985
4259
|
}
|
|
3986
4260
|
runtimeFor() {
|
|
3987
4261
|
return {
|
|
4262
|
+
streamTokens: this.client.options?.streamTokens !== false,
|
|
3988
4263
|
emitEvent: (event, data) => {
|
|
3989
4264
|
;
|
|
3990
4265
|
this.client.socket().emit(event, data);
|
|
@@ -4001,6 +4276,8 @@ var SessionHub = class {
|
|
|
4001
4276
|
}),
|
|
4002
4277
|
ensureConnected: () => this.ensureConnected(),
|
|
4003
4278
|
fetchTurns: (sessionId) => this.client.sessions.getSessionTurns(sessionId),
|
|
4279
|
+
fetchSessionInfo: (sessionId) => this.client.sessions.getSession(sessionId),
|
|
4280
|
+
updateReplay: (sessionId, payload) => this.client.sessions.updateReplaySession(sessionId, payload).then((result) => result.replay_state ?? null),
|
|
4004
4281
|
listDir: (sessionId, dirPath) => this.client.sessions.listDir(sessionId, dirPath),
|
|
4005
4282
|
countFiles: (sessionId, dirPath) => this.client.sessions.countFiles(sessionId, dirPath),
|
|
4006
4283
|
downloadFile: (sessionId, filePath, downloadName) => this.client.sessions.downloadFile(sessionId, filePath, downloadName),
|
|
@@ -4222,7 +4499,7 @@ function resolveAuthToken(options) {
|
|
|
4222
4499
|
|
|
4223
4500
|
// src/version.ts
|
|
4224
4501
|
var SDK_NAME = "agent-client";
|
|
4225
|
-
var SDK_VERSION = true ? "2608.0.
|
|
4502
|
+
var SDK_VERSION = true ? "2608.0.7-beta.0" : "1.1.1";
|
|
4226
4503
|
|
|
4227
4504
|
// src/socket.ts
|
|
4228
4505
|
function withSdkIdentity(auth) {
|
|
@@ -4674,45 +4951,18 @@ function connectEmbedded(options) {
|
|
|
4674
4951
|
};
|
|
4675
4952
|
}
|
|
4676
4953
|
|
|
4677
|
-
// src/schemas/
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
)
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
"memory_enabled?": "boolean",
|
|
4690
|
-
"is_persistent?": "boolean",
|
|
4691
|
-
"is_headless?": "boolean",
|
|
4692
|
-
"viewer_role?": "'owner' | 'viewer'",
|
|
4693
|
-
"template_id?": "string | null",
|
|
4694
|
-
"model?": "string | null",
|
|
4695
|
-
"enable_thinking?": "boolean | null",
|
|
4696
|
-
"solution_id?": "string | null",
|
|
4697
|
-
"solution_ref?": "unknown",
|
|
4698
|
-
"biz_role_id?": "string | null",
|
|
4699
|
-
"solution?": "unknown",
|
|
4700
|
-
"plan_summary?": "string | null",
|
|
4701
|
-
"primary_skill_id?": "string | null",
|
|
4702
|
-
// ship-attack v2:后端 engine.set_bound_skill 写入的"绑定 skill id"。
|
|
4703
|
-
// 未绑定 / 非 ship-attack session 为 null。
|
|
4704
|
-
"bound_skill_id?": "string | null",
|
|
4705
|
-
"replay_state?": "unknown",
|
|
4706
|
-
"is_pinned?": "boolean",
|
|
4707
|
-
"pinned_at?": "string | null",
|
|
4708
|
-
"is_example?": "boolean",
|
|
4709
|
-
"ports?": "unknown",
|
|
4710
|
-
"disable_tools?": "string[]",
|
|
4711
|
-
"runtime_type?": "string | null",
|
|
4712
|
-
"daemon_id?": "string | null",
|
|
4713
|
-
"workspace_path?": "string | null",
|
|
4714
|
-
"match?": "unknown"
|
|
4715
|
-
});
|
|
4954
|
+
// src/schemas/projection-utils.ts
|
|
4955
|
+
function latestPostChatFollowup(turns) {
|
|
4956
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
4957
|
+
const turn = turns[index];
|
|
4958
|
+
if (turn.loop_id === "root" && (turn.role === "user" || turn.role === "assistant") && (turn.kind ?? "message") === "message" && !turn.blocks.some(
|
|
4959
|
+
(block) => ["mode_change", "plan_status", "ask_user_answer"].includes(block.type)
|
|
4960
|
+
)) {
|
|
4961
|
+
return turn.role === "assistant" ? turn.post_chat_followup ?? null : null;
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
return null;
|
|
4965
|
+
}
|
|
4716
4966
|
|
|
4717
4967
|
// src/schemas/solution.ts
|
|
4718
4968
|
var LayoutType = /* @__PURE__ */ ((LayoutType2) => {
|
|
@@ -4745,6 +4995,7 @@ export {
|
|
|
4745
4995
|
BladeApiError,
|
|
4746
4996
|
BladeClient,
|
|
4747
4997
|
ClientProjectionBuilder,
|
|
4998
|
+
DEFAULT_REPLAY_SPEED,
|
|
4748
4999
|
LayoutType,
|
|
4749
5000
|
ModelsResource,
|
|
4750
5001
|
SDK_NAME,
|
|
@@ -4755,6 +5006,7 @@ export {
|
|
|
4755
5006
|
SessionStatus,
|
|
4756
5007
|
Task,
|
|
4757
5008
|
TaskStatus,
|
|
5009
|
+
acceptedPostChatFollowupCompletesLatestRun,
|
|
4758
5010
|
buildMessageContent,
|
|
4759
5011
|
connectEmbedded,
|
|
4760
5012
|
contentPreview,
|
|
@@ -4768,7 +5020,10 @@ export {
|
|
|
4768
5020
|
isCommandEnvelope,
|
|
4769
5021
|
isHiddenInternalMessage,
|
|
4770
5022
|
isInboundEnvelope,
|
|
5023
|
+
latestPostChatFollowup,
|
|
4771
5024
|
normalizeMessageContent,
|
|
5025
|
+
reconcileOptimisticUserTurns,
|
|
5026
|
+
toReplaySnapshot,
|
|
4772
5027
|
transformSlashCommand
|
|
4773
5028
|
};
|
|
4774
5029
|
//# sourceMappingURL=index.js.map
|