@blade-hq/agent-client 2608.0.7-beta.0 → 2608.0.7-beta.2

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/dist/index.js CHANGED
@@ -2,9 +2,6 @@
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
- };
8
5
  function randomState() {
9
6
  const bytes = new Uint8Array(24);
10
7
  crypto.getRandomValues(bytes);
@@ -82,22 +79,18 @@ async function loginWithPopup(client, options = {}) {
82
79
  if (event.data.state !== state) return;
83
80
  const message = event.data;
84
81
  stopCloseWatch();
85
- const closePopup = () => {
86
- try {
87
- popup.close();
88
- } catch {
89
- }
90
- };
82
+ try {
83
+ popup.close();
84
+ } catch {
85
+ }
91
86
  if (message.status === "denied") {
92
- closePopup();
93
87
  settle(() => reject(new Error("\u4F60\u53D6\u6D88\u4E86\u6388\u6743\uFF0C\u672A\u5B8C\u6210\u767B\u5F55\u3002")));
94
88
  return;
95
89
  }
96
90
  if (message.status !== "success" || !message.code) {
97
- settle(() => reject(new Error(AUTH_ERROR_HINTS[message.error ?? ""] ?? message.error ?? "\u767B\u5F55\u5931\u8D25\uFF0C\u672A\u53D6\u5F97\u6388\u6743\u7801\u3002")));
91
+ settle(() => reject(new Error(message.error || "\u767B\u5F55\u5931\u8D25\uFF0C\u672A\u53D6\u5F97\u6388\u6743\u7801\u3002")));
98
92
  return;
99
93
  }
100
- closePopup();
101
94
  client.json("POST", "/api/auth/sdk/token", {
102
95
  code: message.code,
103
96
  state,
@@ -181,24 +174,6 @@ var AuthResource = class {
181
174
  login(options) {
182
175
  return this.client.login(options);
183
176
  }
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
- }
202
177
  /** 清除 login() 存储的令牌。 */
203
178
  logoutToken() {
204
179
  this.client.logoutToken();
@@ -350,11 +325,7 @@ var HeadlessResource = class {
350
325
  socket.on("chat:start", onChatStart);
351
326
  socket.on("chat:end", onChatEnd);
352
327
  socket.on("system:error", onSystemError);
353
- socket.emit("session:subscribe", {
354
- session_id,
355
- event_format: "raw",
356
- stream_tokens: this.client.options.streamTokens !== false
357
- });
328
+ socket.emit("session:subscribe", { session_id, event_format: "raw" });
358
329
  socket.emit(
359
330
  "chat:send",
360
331
  {
@@ -436,47 +407,6 @@ var ModelsResource = class {
436
407
  }
437
408
  };
438
409
 
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
-
480
410
  // src/session/definition.ts
481
411
  var SessionSetupError = class extends Error {
482
412
  constructor(message, stage, sessionId, options) {
@@ -897,28 +827,6 @@ function sourceLoopFor(loopId, loopDescriptions) {
897
827
  if (!description) return null;
898
828
  return { loop_name: loopId, description };
899
829
  }
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
- }
922
830
  function normalizePostChatFollowup(data, assistantEntryId) {
923
831
  const raw = data.suggestions;
924
832
  const suggestions = [];
@@ -930,17 +838,11 @@ function normalizePostChatFollowup(data, assistantEntryId) {
930
838
  }
931
839
  }
932
840
  const recaption = String(data.recaption ?? "");
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;
841
+ if (suggestions.length === 0 && !recaption) return null;
937
842
  return {
938
843
  assistant_entry_id: assistantEntryId,
939
844
  recaption,
940
- suggestions: suggestions.slice(0, 3),
941
- final_artifacts: finalArtifacts,
942
- feedback_eligible: feedbackEligible,
943
- feedback_scope: "latest_result"
845
+ suggestions: suggestions.slice(0, 3)
944
846
  };
945
847
  }
946
848
  function normalizeChildPause(pausePayload) {
@@ -1024,60 +926,11 @@ function toolStatusFromResult(result) {
1024
926
  }
1025
927
 
1026
928
  // 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
- }
1048
929
  function isConversationTurn(turn) {
1049
930
  return (turn.role === "user" || turn.role === "assistant") && (turn.kind ?? "message") === "message" && !turn.blocks.some(
1050
931
  (block) => block.type === "mode_change" || block.type === "plan_status" || block.type === "ask_user_answer"
1051
932
  );
1052
933
  }
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
- }
1081
934
  var ClientProjectionBuilder = class {
1082
935
  activeTurns = /* @__PURE__ */ new Map();
1083
936
  lastTurnIds = /* @__PURE__ */ new Map();
@@ -1160,9 +1013,6 @@ var ClientProjectionBuilder = class {
1160
1013
  return this.onDelta(state, "thinking", "", String(payload.content ?? ""));
1161
1014
  case "llm:content:delta":
1162
1015
  return this.onDelta(state, "text", "", String(payload.content ?? ""));
1163
- case "llm:content:complete":
1164
- replaceTextContent(state, String(payload.content ?? ""));
1165
- return this.syncPatch(state);
1166
1016
  case "llm:tool_call:created":
1167
1017
  return this.onToolCallCreated(state, payload);
1168
1018
  case "llm:response:done":
@@ -1452,9 +1302,6 @@ var ClientProjectionBuilder = class {
1452
1302
  if (typeof payload.usage === "object" && payload.usage !== null) {
1453
1303
  state.usage = { ...payload.usage };
1454
1304
  }
1455
- if ("content" in payload) {
1456
- replaceTextContent(state, completedResponseText(payload.content));
1457
- }
1458
1305
  const toolCalls = payload.tool_calls;
1459
1306
  if (Array.isArray(toolCalls)) {
1460
1307
  for (const raw of toolCalls) {
@@ -2258,11 +2105,6 @@ var SessionsResource = class {
2258
2105
  listSessionsWithSkillData() {
2259
2106
  return this.client.json("GET", "/api/sessions/with-skill-data");
2260
2107
  }
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
- }
2266
2108
  createSession(intent, template_id, primary_skill_id) {
2267
2109
  return this.createSessionWithRequest({ intent, template_id, primary_skill_id });
2268
2110
  }
@@ -2288,16 +2130,12 @@ var SessionsResource = class {
2288
2130
  pinSession(sessionId, pinned) {
2289
2131
  return this.client.json("PATCH", `/api/sessions/${sessionId}/pin`, { pinned });
2290
2132
  }
2291
- startReplaySession(sourceSessionId, speed = DEFAULT_REPLAY_SPEED) {
2133
+ startReplaySession(sourceSessionId, speed = 5) {
2292
2134
  return this.client.json("POST", `/api/sessions/${sourceSessionId}/replay`, { speed });
2293
2135
  }
2294
2136
  updateReplaySession(sessionId, payload) {
2295
2137
  return this.client.json("PATCH", `/api/sessions/${sessionId}/replay`, payload);
2296
2138
  }
2297
- /** 查询会话能否作为回放来源;不支持时 reason 可直接展示,用来提前禁用入口。 */
2298
- getReplayPreview(sessionId) {
2299
- return this.client.json("GET", `/api/sessions/${sessionId}/replay/preview`);
2300
- }
2301
2139
  updateSharing(sessionId, shared) {
2302
2140
  return this.client.json("PATCH", `/api/sessions/${sessionId}/sharing`, { shared });
2303
2141
  }
@@ -2329,16 +2167,6 @@ var SessionsResource = class {
2329
2167
  );
2330
2168
  return projectHistory(entries);
2331
2169
  }
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
- }
2342
2170
  getSessionContextStats(sessionId, init) {
2343
2171
  return this.client.jsonFromInit(`/api/sessions/${sessionId}/context-stats`, init);
2344
2172
  }
@@ -2761,15 +2589,10 @@ function contentPreview(content, maxLen = 80) {
2761
2589
  const text = getTextContent(content).trim();
2762
2590
  return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
2763
2591
  }
2764
- function transformSlashCommand(skillName, rawInput, { local = true, installed = true } = {}) {
2592
+ function transformSlashCommand(skillName, rawInput) {
2765
2593
  const prompt = rawInput.replace(`<skill>${skillName}</skill>`, "").trim();
2766
- if (local) {
2767
- return prompt ? `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1
2594
+ return prompt ? `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1
2768
2595
  ${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`;
2773
2596
  }
2774
2597
  var ATTACHMENT_TAG_RE = /\[附件:\s*([^\]]+)\](?:[ \t]*已上传到工作区路径:[ \t]*([^\r\n]+))?/g;
2775
2598
  var CONTEXT_PART_RE = /^\[上下文:\s*([^\]]+)\]\n([\s\S]*)$/;
@@ -2855,14 +2678,6 @@ function groupMessagesByLoop(messages) {
2855
2678
  }
2856
2679
 
2857
2680
  // 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
- }
2866
2681
  function createInitialSessionState(sessionId) {
2867
2682
  return {
2868
2683
  sessionId,
@@ -2875,9 +2690,7 @@ function createInitialSessionState(sessionId) {
2875
2690
  errorMessage: null,
2876
2691
  askAnswers: {},
2877
2692
  agentLoops: {},
2878
- activeCompaction: null,
2879
- replay: null,
2880
- viewerRole: null
2693
+ activeCompaction: null
2881
2694
  };
2882
2695
  }
2883
2696
  function parseAgentDescription(argumentsJson) {
@@ -3140,25 +2953,6 @@ function orderTurns(turns) {
3140
2953
  }
3141
2954
  return turns;
3142
2955
  }
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
- }
3162
2956
  function areAgentLoopsEqual(left, right) {
3163
2957
  if (!left) return Object.keys(right).length === 0;
3164
2958
  const leftEntries = Object.entries(left);
@@ -3170,7 +2964,7 @@ function areAgentLoopsEqual(left, right) {
3170
2964
  });
3171
2965
  }
3172
2966
  function withTurns(state, turns) {
3173
- const orderedTurns = orderTurns(reconcileOptimisticUserTurns(state.turns, turns));
2967
+ const orderedTurns = orderTurns(turns);
3174
2968
  const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
3175
2969
  const isWaitingForInput = orderedTurns.some(
3176
2970
  (turn) => turn.tool_calls.some((toolCall) => toolCall.status === "awaiting_answer")
@@ -3180,13 +2974,7 @@ function withTurns(state, turns) {
3180
2974
  const preservedErrors = lastTurnId ? state.messages.filter(
3181
2975
  (m) => m.role === "error" && typeof m.entry_id === "string" && m.entry_id.startsWith(`${ERROR_ANCHOR_PREFIX}${lastTurnId}:`)
3182
2976
  ) : [];
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;
2977
+ const mergedMessages = preservedErrors.length > 0 ? [...messages, ...preservedErrors] : messages;
3190
2978
  const latestMode = [...orderedTurns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
3191
2979
  return {
3192
2980
  ...state,
@@ -3221,17 +3009,14 @@ function addUserMessage(state, content) {
3221
3009
  }
3222
3010
  function upsertTurn(state, turn) {
3223
3011
  let existing = [...state.turns];
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
- );
3012
+ if (turn.role === "user" && !turn.turn_id.startsWith("local-user-")) {
3013
+ existing = existing.filter((t) => !t.turn_id.startsWith("local-user-"));
3229
3014
  }
3230
- const index = existing.findIndex((item) => item.turn_id === nextTurn.turn_id);
3015
+ const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
3231
3016
  if (index >= 0) {
3232
- existing[index] = nextTurn;
3017
+ existing[index] = turn;
3233
3018
  } else {
3234
- existing.push(nextTurn);
3019
+ existing.push(turn);
3235
3020
  }
3236
3021
  return withTurns(state, existing);
3237
3022
  }
@@ -3335,7 +3120,7 @@ function extractTerminalChatEndStatus(events) {
3335
3120
  }
3336
3121
  return null;
3337
3122
  }
3338
- function hasChatRunEvent2(events) {
3123
+ function hasChatRunEvent(events) {
3339
3124
  return events.some(
3340
3125
  (event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
3341
3126
  );
@@ -3356,11 +3141,6 @@ var AgentSession = class _AgentSession {
3356
3141
  patchFlushCancel = null;
3357
3142
  joined = false;
3358
3143
  joinPending = null;
3359
- // 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
3360
- // 请求响应可能乱序回来,晚到的旧值会把已经退出的会话又显示成回放中;用
3361
- // 序号丢弃过期响应又会在失败时留下中间态。串行执行就没有这些情况:每个
3362
- // 响应都是当时最新的,失败也只影响自己。
3363
- replayQueue = Promise.resolve();
3364
3144
  pendingReplayMessage = null;
3365
3145
  pendingReplayMode;
3366
3146
  recentChatEndAt = 0;
@@ -3387,15 +3167,10 @@ var AgentSession = class _AgentSession {
3387
3167
  const session = new _AgentSession(
3388
3168
  sessionId,
3389
3169
  {
3390
- streamTokens: false,
3391
3170
  emitEvent: readonlyError,
3392
3171
  emitWithAck: async () => readonlyError(),
3393
3172
  ensureConnected: readonlyError,
3394
3173
  fetchTurns: async () => turns,
3395
- // 静态历史没有回放身份可言,给个空详情即可,不要抛错——_bootstrap
3396
- // 不会跑到这里,但保持只读语义一致。
3397
- fetchSessionInfo: async () => ({ replay_state: null }),
3398
- updateReplay: async () => readonlyError(),
3399
3174
  listDir: async () => readonlyError(),
3400
3175
  countFiles: async () => readonlyError(),
3401
3176
  downloadFile: async () => readonlyError(),
@@ -3687,13 +3462,10 @@ var AgentSession = class _AgentSession {
3687
3462
  this.joinPending = null;
3688
3463
  try {
3689
3464
  await this.ensureJoined();
3690
- await Promise.all([
3691
- this.syncTurnsFromHistory({
3692
- clearOnEmpty: true,
3693
- preserveChangesSince: turnsBeforeRejoin
3694
- }),
3695
- this.syncReplayFromSession()
3696
- ]);
3465
+ await this.syncTurnsFromHistory({
3466
+ clearOnEmpty: true,
3467
+ preserveChangesSince: turnsBeforeRejoin
3468
+ });
3697
3469
  } catch {
3698
3470
  }
3699
3471
  }
@@ -3701,40 +3473,6 @@ var AgentSession = class _AgentSession {
3701
3473
  async _bootstrap() {
3702
3474
  await this.syncTurnsFromHistory({ suppressErrors: false });
3703
3475
  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;
3738
3476
  }
3739
3477
  /** @internal */
3740
3478
  _handleTurnStart(turn) {
@@ -3761,7 +3499,7 @@ var AgentSession = class _AgentSession {
3761
3499
  _handleTurnEvents(events) {
3762
3500
  if (!events.length) return;
3763
3501
  const terminalChatEndStatus = extractTerminalChatEndStatus(events);
3764
- if (!terminalChatEndStatus && hasChatRunEvent2(events)) {
3502
+ if (!terminalChatEndStatus && hasChatRunEvent(events)) {
3765
3503
  this.markRunningIfIdle();
3766
3504
  }
3767
3505
  if (this.state.turns.length) {
@@ -3775,8 +3513,8 @@ var AgentSession = class _AgentSession {
3775
3513
  this.emitter.emit("workspaceChanged", {});
3776
3514
  }
3777
3515
  }
3778
- if (terminalChatEndStatus || acceptedPostChatFollowupCompletesLatestRun(events, updates)) {
3779
- this.completeChat(terminalChatEndStatus ?? "completed");
3516
+ if (terminalChatEndStatus) {
3517
+ this.finishStreaming(terminalChatEndStatus);
3780
3518
  }
3781
3519
  }
3782
3520
  /** @internal */
@@ -3786,7 +3524,10 @@ var AgentSession = class _AgentSession {
3786
3524
  /** @internal */
3787
3525
  _handleChatEnd(status) {
3788
3526
  this.flushPatches();
3789
- this.completeChat(status);
3527
+ this.finishStreaming(status);
3528
+ this.recentChatEndAt = Date.now();
3529
+ void this.syncTurnsFromHistory();
3530
+ this.emitter.emit("chatEnd", { status });
3790
3531
  }
3791
3532
  /** @internal */
3792
3533
  _handleSystemError(message) {
@@ -3811,10 +3552,6 @@ var AgentSession = class _AgentSession {
3811
3552
  if (data.status) {
3812
3553
  this.update((s) => ({ ...s, status: data.status ?? s.status }));
3813
3554
  }
3814
- if (data.replay_state !== void 0) {
3815
- const replay = toReplaySnapshot(data.replay_state);
3816
- this.update((s) => ({ ...s, replay }));
3817
- }
3818
3555
  this.emitter.emit("sessionUpdated", {
3819
3556
  intent: data.intent,
3820
3557
  status: data.status,
@@ -3930,12 +3667,6 @@ var AgentSession = class _AgentSession {
3930
3667
  return next;
3931
3668
  });
3932
3669
  }
3933
- completeChat(status) {
3934
- this.finishStreaming(status);
3935
- this.recentChatEndAt = Date.now();
3936
- void this.syncTurnsFromHistory();
3937
- this.emitter.emit("chatEnd", { status });
3938
- }
3939
3670
  applyTurn(turn) {
3940
3671
  this.flushPatches();
3941
3672
  const previous = this.state;
@@ -4118,12 +3849,7 @@ var AgentSession = class _AgentSession {
4118
3849
  if (this.joinPending) return this.joinPending;
4119
3850
  const join = this.runtime.emitWithAck(
4120
3851
  "session:subscribe",
4121
- {
4122
- session_id: this.sessionId,
4123
- event_format: "raw",
4124
- resume: true,
4125
- stream_tokens: this.runtime.streamTokens
4126
- },
3852
+ { session_id: this.sessionId, event_format: "raw", resume: true },
4127
3853
  5e3
4128
3854
  ).then((response) => {
4129
3855
  if (response && response.ok === false) {
@@ -4259,7 +3985,6 @@ var SessionHub = class {
4259
3985
  }
4260
3986
  runtimeFor() {
4261
3987
  return {
4262
- streamTokens: this.client.options?.streamTokens !== false,
4263
3988
  emitEvent: (event, data) => {
4264
3989
  ;
4265
3990
  this.client.socket().emit(event, data);
@@ -4276,8 +4001,6 @@ var SessionHub = class {
4276
4001
  }),
4277
4002
  ensureConnected: () => this.ensureConnected(),
4278
4003
  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),
4281
4004
  listDir: (sessionId, dirPath) => this.client.sessions.listDir(sessionId, dirPath),
4282
4005
  countFiles: (sessionId, dirPath) => this.client.sessions.countFiles(sessionId, dirPath),
4283
4006
  downloadFile: (sessionId, filePath, downloadName) => this.client.sessions.downloadFile(sessionId, filePath, downloadName),
@@ -4499,7 +4222,7 @@ function resolveAuthToken(options) {
4499
4222
 
4500
4223
  // src/version.ts
4501
4224
  var SDK_NAME = "agent-client";
4502
- var SDK_VERSION = true ? "2608.0.7-beta.0" : "1.1.1";
4225
+ var SDK_VERSION = true ? "2608.0.7-beta.2" : "1.1.1";
4503
4226
 
4504
4227
  // src/socket.ts
4505
4228
  function withSdkIdentity(auth) {
@@ -4892,6 +4615,87 @@ function parseXhrHeaders(rawHeaders) {
4892
4615
  return headers;
4893
4616
  }
4894
4617
 
4618
+ // src/platform-endpoints.ts
4619
+ var PLATFORM_SERVICE_NAMES = [
4620
+ "os",
4621
+ "hub",
4622
+ "agent",
4623
+ "oauth",
4624
+ "llmGateway",
4625
+ "gitea"
4626
+ ];
4627
+ var EMPTY_PLATFORM_ENDPOINTS = Object.freeze({
4628
+ services: Object.freeze({})
4629
+ });
4630
+ function defaultDocumentBaseUrl() {
4631
+ if (typeof document === "undefined") return void 0;
4632
+ if (document.querySelector("base[href]")) return document.baseURI;
4633
+ if (typeof location !== "undefined" && location.origin) return `${location.origin}/`;
4634
+ return document.baseURI;
4635
+ }
4636
+ function isRecord6(value) {
4637
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4638
+ }
4639
+ function normalizeServiceUrl(value) {
4640
+ if (typeof value !== "string" || !value.trim()) return null;
4641
+ if (value.includes("?") || value.includes("#")) return null;
4642
+ try {
4643
+ const url = new URL(value.trim());
4644
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
4645
+ if (url.username || url.password || url.search || url.hash) return null;
4646
+ return url.toString().replace(/\/$/, "");
4647
+ } catch {
4648
+ return null;
4649
+ }
4650
+ }
4651
+ function parsePlatformEndpoints(value) {
4652
+ if (!isRecord6(value) || !isRecord6(value.services)) return null;
4653
+ const services = {};
4654
+ for (const name of PLATFORM_SERVICE_NAMES) {
4655
+ if (!(name in value.services)) continue;
4656
+ const url = normalizeServiceUrl(value.services[name]);
4657
+ if (!url) return null;
4658
+ services[name] = url;
4659
+ }
4660
+ return { services };
4661
+ }
4662
+ async function loadPlatformEndpoints(options = {}) {
4663
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
4664
+ const baseUrl = options.baseUrl ?? defaultDocumentBaseUrl();
4665
+ if (!fetchImpl || !baseUrl) return EMPTY_PLATFORM_ENDPOINTS;
4666
+ const controller = new AbortController();
4667
+ const timeout = globalThis.setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
4668
+ try {
4669
+ const response = await fetchImpl(new URL("config.json", baseUrl), {
4670
+ cache: "no-store",
4671
+ credentials: "same-origin",
4672
+ headers: { Accept: "application/json" },
4673
+ signal: controller.signal
4674
+ });
4675
+ if (!response.ok) return EMPTY_PLATFORM_ENDPOINTS;
4676
+ return parsePlatformEndpoints(await response.json()) ?? EMPTY_PLATFORM_ENDPOINTS;
4677
+ } catch {
4678
+ return EMPTY_PLATFORM_ENDPOINTS;
4679
+ } finally {
4680
+ globalThis.clearTimeout(timeout);
4681
+ }
4682
+ }
4683
+ function resolveServiceUrl(endpoints, name, path = "") {
4684
+ const baseUrl = endpoints.services[name];
4685
+ if (!baseUrl) return null;
4686
+ if (!path) return baseUrl;
4687
+ const pathReference = path.trimStart();
4688
+ if (path.includes("\\") || pathReference.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(pathReference)) {
4689
+ return null;
4690
+ }
4691
+ const base = new URL(`${baseUrl}/`);
4692
+ const target = new URL(path.replace(/^\/+/, ""), base);
4693
+ if (target.origin !== base.origin || !target.pathname.startsWith(base.pathname)) {
4694
+ return null;
4695
+ }
4696
+ return target.toString();
4697
+ }
4698
+
4895
4699
  // src/commands/protocol.ts
4896
4700
  function isCommandEnvelope(value) {
4897
4701
  if (typeof value !== "object" || value === null) return false;
@@ -4951,18 +4755,45 @@ function connectEmbedded(options) {
4951
4755
  };
4952
4756
  }
4953
4757
 
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
- }
4758
+ // src/schemas/session.ts
4759
+ import { type } from "arktype";
4760
+ var SessionStatus = type(
4761
+ "'created' | 'running' | 'completed' | 'failed' | 'interrupted' | 'waiting_for_input'"
4762
+ );
4763
+ var SessionInfo = type({
4764
+ id: "string",
4765
+ intent: "string",
4766
+ status: SessionStatus,
4767
+ created_at: "string",
4768
+ updated_at: "string",
4769
+ "shared?": "boolean",
4770
+ "memory_enabled?": "boolean",
4771
+ "is_persistent?": "boolean",
4772
+ "is_headless?": "boolean",
4773
+ "viewer_role?": "'owner' | 'viewer'",
4774
+ "template_id?": "string | null",
4775
+ "model?": "string | null",
4776
+ "enable_thinking?": "boolean | null",
4777
+ "solution_id?": "string | null",
4778
+ "solution_ref?": "unknown",
4779
+ "biz_role_id?": "string | null",
4780
+ "solution?": "unknown",
4781
+ "plan_summary?": "string | null",
4782
+ "primary_skill_id?": "string | null",
4783
+ // ship-attack v2:后端 engine.set_bound_skill 写入的"绑定 skill id"。
4784
+ // 未绑定 / 非 ship-attack session 为 null。
4785
+ "bound_skill_id?": "string | null",
4786
+ "replay_state?": "unknown",
4787
+ "is_pinned?": "boolean",
4788
+ "pinned_at?": "string | null",
4789
+ "is_example?": "boolean",
4790
+ "ports?": "unknown",
4791
+ "disable_tools?": "string[]",
4792
+ "runtime_type?": "string | null",
4793
+ "daemon_id?": "string | null",
4794
+ "workspace_path?": "string | null",
4795
+ "match?": "unknown"
4796
+ });
4966
4797
 
4967
4798
  // src/schemas/solution.ts
4968
4799
  var LayoutType = /* @__PURE__ */ ((LayoutType2) => {
@@ -4995,7 +4826,7 @@ export {
4995
4826
  BladeApiError,
4996
4827
  BladeClient,
4997
4828
  ClientProjectionBuilder,
4998
- DEFAULT_REPLAY_SPEED,
4829
+ EMPTY_PLATFORM_ENDPOINTS,
4999
4830
  LayoutType,
5000
4831
  ModelsResource,
5001
4832
  SDK_NAME,
@@ -5006,7 +4837,6 @@ export {
5006
4837
  SessionStatus,
5007
4838
  Task,
5008
4839
  TaskStatus,
5009
- acceptedPostChatFollowupCompletesLatestRun,
5010
4840
  buildMessageContent,
5011
4841
  connectEmbedded,
5012
4842
  contentPreview,
@@ -5020,10 +4850,9 @@ export {
5020
4850
  isCommandEnvelope,
5021
4851
  isHiddenInternalMessage,
5022
4852
  isInboundEnvelope,
5023
- latestPostChatFollowup,
4853
+ loadPlatformEndpoints,
5024
4854
  normalizeMessageContent,
5025
- reconcileOptimisticUserTurns,
5026
- toReplaySnapshot,
4855
+ resolveServiceUrl,
5027
4856
  transformSlashCommand
5028
4857
  };
5029
4858
  //# sourceMappingURL=index.js.map