@blade-hq/agent-client 2610.0.0-beta.2 → 2610.0.0-beta.21

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
@@ -1023,6 +1023,87 @@ function toolStatusFromResult(result) {
1023
1023
  return inferToolStatus(text);
1024
1024
  }
1025
1025
 
1026
+ // src/schemas/context.ts
1027
+ var BUILTIN_CONTEXT_TITLES = {
1028
+ "agent-instructions": "\u9879\u76EE\u8BF4\u660E",
1029
+ "skill-catalog": "\u53EF\u7528\u80FD\u529B",
1030
+ "runtime-context": "\u5F53\u524D\u5DE5\u4F5C\u73AF\u5883"
1031
+ };
1032
+ var BUILTIN_CONTEXT_DETAILS = {
1033
+ "agent-instructions": "\u8FD9\u4E9B\u8BF4\u660E\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u7406\u89E3\u5F53\u524D\u9879\u76EE\u548C\u5DE5\u4F5C\u8981\u6C42\u3002",
1034
+ "skill-catalog": "\u8FD9\u4E9B\u80FD\u529B\u4F1A\u5E2E\u52A9\u667A\u80FD\u4F53\u9009\u62E9\u5408\u9002\u7684\u65B9\u5F0F\u5B8C\u6210\u4EFB\u52A1\u3002",
1035
+ "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"
1036
+ };
1037
+ function nonEmptyString(value) {
1038
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1039
+ }
1040
+ function contextProjectionData(fields) {
1041
+ const contextKind = nonEmptyString(fields.context_kind);
1042
+ const contextKey = nonEmptyString(fields.context_key);
1043
+ const contextName = nonEmptyString(fields.context_name);
1044
+ const contextRevision = nonEmptyString(fields.context_revision);
1045
+ if (!contextKind || !contextKey || !contextName || !contextRevision || typeof fields.context_order !== "number" || !Number.isFinite(fields.context_order) || !fields.context_action) {
1046
+ return null;
1047
+ }
1048
+ return {
1049
+ context_kind: contextKind,
1050
+ context_key: contextKey,
1051
+ context_name: contextName,
1052
+ context_order: fields.context_order,
1053
+ context_action: fields.context_action,
1054
+ context_revision: contextRevision,
1055
+ display_title: nonEmptyString(fields.display_title),
1056
+ display_summary: nonEmptyString(fields.display_summary),
1057
+ sources: Array.isArray(fields.sources) ? fields.sources : []
1058
+ };
1059
+ }
1060
+ function getContextDisplayState(context) {
1061
+ const title = BUILTIN_CONTEXT_TITLES[context.context_kind] ?? nonEmptyString(context.display_title) ?? "\u5BF9\u8BDD\u80CC\u666F";
1062
+ const isRemoved = context.context_action === "removed";
1063
+ 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" : "\u5DF2\u66F4\u65B0\u672C\u6B21\u5BF9\u8BDD\u80CC\u666F");
1064
+ 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";
1065
+ return { title, summary, detail, isRemoved };
1066
+ }
1067
+
1068
+ // src/shared/projection/context.ts
1069
+ function isRecord2(value) {
1070
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1071
+ }
1072
+ function buildContextTurn(entry, sequence) {
1073
+ if (entry.kind !== "context" || !isRecord2(entry.source) || !isRecord2(entry.data)) {
1074
+ return null;
1075
+ }
1076
+ const display = isRecord2(entry.data.display) ? entry.data.display : {};
1077
+ const context = contextProjectionData({
1078
+ context_kind: typeof entry.source.kind === "string" ? entry.source.kind : null,
1079
+ context_key: typeof entry.source.key === "string" ? entry.source.key : null,
1080
+ context_name: typeof entry.data.contribution_name === "string" ? entry.data.contribution_name : null,
1081
+ context_order: typeof entry.data.order === "number" ? entry.data.order : null,
1082
+ context_action: entry.data.action === "published" || entry.data.action === "retained" || entry.data.action === "removed" ? entry.data.action : null,
1083
+ context_revision: typeof entry.data.revision === "string" ? entry.data.revision : null,
1084
+ display_title: typeof display.title === "string" ? display.title : null,
1085
+ display_summary: typeof display.summary === "string" ? display.summary : null,
1086
+ sources: Array.isArray(display.sources) ? display.sources.filter(isRecord2) : []
1087
+ });
1088
+ if (!context) return null;
1089
+ const turnId = typeof entry.id === "string" && entry.id ? entry.id : `context-${sequence}`;
1090
+ return {
1091
+ id: turnId,
1092
+ sequence,
1093
+ turn_id: turnId,
1094
+ loop_id: typeof entry.loop_name === "string" && entry.loop_name ? entry.loop_name : "root",
1095
+ kind: "context",
1096
+ role: "system",
1097
+ status: "completed",
1098
+ blocks: [],
1099
+ tool_calls: [],
1100
+ model: null,
1101
+ usage: null,
1102
+ duration_ms: 0,
1103
+ ...context
1104
+ };
1105
+ }
1106
+
1026
1107
  // src/shared/projection/builder.ts
1027
1108
  function hasChatRunEvent(events) {
1028
1109
  return events.some(
@@ -1117,6 +1198,8 @@ var ClientProjectionBuilder = class {
1117
1198
  return this.onMemoryInject(loopId, payload);
1118
1199
  case "memory:inject:none":
1119
1200
  return null;
1201
+ case "context:entry":
1202
+ return this.onContextEntry(payload);
1120
1203
  case "loop:turn":
1121
1204
  return this.onLoopTurn(loopId, payload);
1122
1205
  case "mode:change":
@@ -1165,6 +1248,8 @@ var ClientProjectionBuilder = class {
1165
1248
  return this.syncPatch(state);
1166
1249
  case "llm:tool_call:created":
1167
1250
  return this.onToolCallCreated(state, payload);
1251
+ case "llm:tool_call:arguments:delta":
1252
+ return this.onToolCallArgumentsDelta(state, payload);
1168
1253
  case "llm:response:done":
1169
1254
  return this.onResponseDone(state, loopId, payload);
1170
1255
  case "tool:result:delta":
@@ -1232,6 +1317,11 @@ var ClientProjectionBuilder = class {
1232
1317
  }
1233
1318
  return null;
1234
1319
  }
1320
+ onContextEntry(payload) {
1321
+ if (!isRecord3(payload.entry)) return null;
1322
+ const turn = buildContextTurn(payload.entry, this.nextSeq());
1323
+ return turn ? [{ kind: "upsert", turn }] : null;
1324
+ }
1235
1325
  onLoopTurn(loopId, payload) {
1236
1326
  let turnId = String(payload.turn_id ?? this.nextTurnId(`turn:${loopId}`));
1237
1327
  let memoryRefs = null;
@@ -1288,11 +1378,11 @@ var ClientProjectionBuilder = class {
1288
1378
  loopId,
1289
1379
  toolCallId: optStr(payload.tool_call_id) ?? void 0,
1290
1380
  toolName: toolNameFromPayload(payload) ?? void 0,
1291
- workspacePath: isRecord2(payload.workspace_change) ? optStr(payload.workspace_change.to) ?? void 0 : void 0
1381
+ workspacePath: isRecord3(payload.workspace_change) ? optStr(payload.workspace_change.to) ?? void 0 : void 0
1292
1382
  }
1293
1383
  ];
1294
1384
  const change = payload.workspace_change;
1295
- if (isRecord2(change)) {
1385
+ if (isRecord3(change)) {
1296
1386
  const source = optStr(change.source) ?? "";
1297
1387
  const projectName = optStr(change.project_name) ?? "";
1298
1388
  const fromPath = optStr(change.from) ?? "";
@@ -1430,13 +1520,7 @@ var ClientProjectionBuilder = class {
1430
1520
  }
1431
1521
  onToolCallCreated(state, payload) {
1432
1522
  if ("arguments_delta" in payload) {
1433
- const delta = String(payload.arguments_delta ?? "");
1434
- const toolCallId2 = String(payload.id ?? "");
1435
- if (delta && toolCallId2) {
1436
- appendToolCallArguments(state, toolCallId2, delta);
1437
- return this.syncPatch(state);
1438
- }
1439
- return null;
1523
+ return this.onToolCallArgumentsDelta(state, payload);
1440
1524
  }
1441
1525
  const toolCallId = String(payload.id ?? "");
1442
1526
  const toolName = toolNameFromPayload(payload);
@@ -1445,6 +1529,13 @@ var ClientProjectionBuilder = class {
1445
1529
  upsertToolCall(state, toolCallId, toolName, displayName, args);
1446
1530
  return this.syncPatch(state);
1447
1531
  }
1532
+ onToolCallArgumentsDelta(state, payload) {
1533
+ const delta = String(payload.arguments_delta ?? "");
1534
+ const toolCallId = String(payload.id ?? "");
1535
+ if (!delta || !toolCallId) return null;
1536
+ appendToolCallArguments(state, toolCallId, delta);
1537
+ return this.syncPatch(state);
1538
+ }
1448
1539
  onResponseDone(state, loopId, payload) {
1449
1540
  state.responseDone = true;
1450
1541
  const model = optStr(payload.model);
@@ -1754,7 +1845,7 @@ var ClientProjectionBuilder = class {
1754
1845
  return `${prefix}:${this.syntheticCounter}:${hex}`;
1755
1846
  }
1756
1847
  };
1757
- function isRecord2(value) {
1848
+ function isRecord3(value) {
1758
1849
  return typeof value === "object" && value !== null && !Array.isArray(value);
1759
1850
  }
1760
1851
  function optStr(value) {
@@ -1790,7 +1881,7 @@ function projectHistory(entries) {
1790
1881
  const kind = String(entry.kind ?? "");
1791
1882
  const loopId = String(entry.loop_name ?? "root");
1792
1883
  const id = String(entry.id ?? `history-${sequence + 1}`);
1793
- if (kind === "post_chat_followup" && isRecord3(entry.data)) {
1884
+ if (kind === "post_chat_followup" && isRecord4(entry.data)) {
1794
1885
  const assistantEntryId = String(entry.data.assistant_entry_id ?? "");
1795
1886
  if (latestRootMessage?.role === "assistant" && latestRootMessage.turn_id === assistantEntryId) {
1796
1887
  const followup = normalizePostChatFollowup(entry.data, assistantEntryId);
@@ -1798,7 +1889,15 @@ function projectHistory(entries) {
1798
1889
  }
1799
1890
  continue;
1800
1891
  }
1801
- if (kind === "message" && isRecord3(entry.message)) {
1892
+ if (kind === "context") {
1893
+ const turn = buildContextTurn(entry, sequence + 1);
1894
+ if (turn) {
1895
+ sequence += 1;
1896
+ turns.push(turn);
1897
+ }
1898
+ continue;
1899
+ }
1900
+ if (kind === "message" && isRecord4(entry.message)) {
1802
1901
  const message = entry.message;
1803
1902
  const role = String(message.role ?? "");
1804
1903
  if (role === "tool") {
@@ -1830,7 +1929,7 @@ function projectHistory(entries) {
1830
1929
  blocks,
1831
1930
  tool_calls: toolCalls,
1832
1931
  model: stringOrNull(message.model),
1833
- usage: isRecord3(message._usage) ? { ...message._usage } : null,
1932
+ usage: isRecord4(message._usage) ? { ...message._usage } : null,
1834
1933
  duration_ms: numberOrZero(message._duration_ms),
1835
1934
  started_at: typeof entry.timestamp === "string" ? entry.timestamp : void 0
1836
1935
  };
@@ -1849,7 +1948,7 @@ function projectHistory(entries) {
1849
1948
  if (kind === "mode_change") {
1850
1949
  turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
1851
1950
  } else if (kind === "workspace_change") {
1852
- const change = isRecord3(entry.data) ? entry.data : {};
1951
+ const change = isRecord4(entry.data) ? entry.data : {};
1853
1952
  const source = stringOrNull(change.source) ?? "";
1854
1953
  const projectName = stringOrNull(change.project_name) ?? "";
1855
1954
  const fromPath = stringOrNull(change.from) ?? "";
@@ -1890,7 +1989,7 @@ function projectHistory(entries) {
1890
1989
  } else if (kind === "compaction" || kind === "tool_result_archive") {
1891
1990
  const data = entry.data ?? {};
1892
1991
  const compactionId = String(data.compaction_id ?? entry.id ?? `history-${sequence + 1}`);
1893
- let content = isRecord3(entry.message) ? entry.message.content : void 0;
1992
+ let content = isRecord4(entry.message) ? entry.message.content : void 0;
1894
1993
  if (kind === "tool_result_archive") {
1895
1994
  const archivedCount = Array.isArray(data.archived_files) ? data.archived_files.length : 0;
1896
1995
  content = `<compaction-summary>
@@ -1905,11 +2004,11 @@ function projectHistory(entries) {
1905
2004
  );
1906
2005
  } else if (kind === "child_pause") {
1907
2006
  const data = entry.data ?? {};
1908
- const sourceLoop = isRecord3(data.source_loop) ? data.source_loop : {};
2007
+ const sourceLoop = isRecord4(data.source_loop) ? data.source_loop : {};
1909
2008
  const childLoopId = String(data.child_loop_name ?? sourceLoop.name ?? "");
1910
2009
  const childToolCallId = String(data.child_pause_tool_call_id ?? "");
1911
2010
  const parentToolCallId = String(data.parent_fork_tool_call_id ?? "");
1912
- const pauseToolData = isRecord3(data.pause_tool_data) ? data.pause_tool_data : {};
2011
+ const pauseToolData = isRecord4(data.pause_tool_data) ? data.pause_tool_data : {};
1913
2012
  const description = String(sourceLoop.description ?? "");
1914
2013
  applyAskUserPauseToTurn(
1915
2014
  latestAssistantByLoop.get(childLoopId),
@@ -1997,15 +2096,15 @@ function parseToolArguments(value) {
1997
2096
  if (!value) return {};
1998
2097
  try {
1999
2098
  const parsed = JSON.parse(value);
2000
- return isRecord3(parsed) ? parsed : {};
2099
+ return isRecord4(parsed) ? parsed : {};
2001
2100
  } catch {
2002
2101
  return {};
2003
2102
  }
2004
2103
  }
2005
2104
  function buildToolCalls(value) {
2006
2105
  if (!Array.isArray(value)) return [];
2007
- return value.filter(isRecord3).map((raw) => {
2008
- const fn = isRecord3(raw.function) ? raw.function : {};
2106
+ return value.filter(isRecord4).map((raw) => {
2107
+ const fn = isRecord4(raw.function) ? raw.function : {};
2009
2108
  const name = String(fn.name ?? "");
2010
2109
  return {
2011
2110
  id: String(raw.id ?? ""),
@@ -2025,7 +2124,7 @@ function buildBlocks(message, toolCalls) {
2025
2124
  blocks.push({ type: "text", content: displayContent });
2026
2125
  } else if (Array.isArray(stored)) {
2027
2126
  for (const raw of stored) {
2028
- if (!isRecord3(raw)) continue;
2127
+ if (!isRecord4(raw)) continue;
2029
2128
  const type3 = String(raw.type ?? "");
2030
2129
  if (type3 === "thinking") blocks.push({ type: type3, content: raw.thinking ?? raw.content ?? "" });
2031
2130
  if (type3 === "text") blocks.push({ type: type3, content: raw.text ?? raw.content ?? "" });
@@ -2058,7 +2157,7 @@ function markerTurn(id, sequence, loopId, type3, content, toolCallId = null) {
2058
2157
  duration_ms: 0
2059
2158
  };
2060
2159
  }
2061
- function isRecord3(value) {
2160
+ function isRecord4(value) {
2062
2161
  return typeof value === "object" && value !== null && !Array.isArray(value);
2063
2162
  }
2064
2163
  function stringOrNull(value) {
@@ -2903,10 +3002,10 @@ function inferLoopStatusFromMessages(messages) {
2903
3002
  }
2904
3003
  function inferLoopStatusFromTurns(turns, messages) {
2905
3004
  const latestAgentNotification = [...turns].reverse().flatMap((turn) => turn.blocks).find((block) => {
2906
- if (block.type !== "system_notification" || !isRecord4(block.content)) return false;
3005
+ if (block.type !== "system_notification" || !isRecord5(block.content)) return false;
2907
3006
  return block.content.notification_type === "agent:start" || block.content.notification_type === "agent:end";
2908
3007
  });
2909
- if (latestAgentNotification?.type === "system_notification" && isRecord4(latestAgentNotification.content)) {
3008
+ if (latestAgentNotification?.type === "system_notification" && isRecord5(latestAgentNotification.content)) {
2910
3009
  const notificationType = latestAgentNotification.content.notification_type;
2911
3010
  const status = latestAgentNotification.content.status;
2912
3011
  if (notificationType === "agent:start" || status === "running") return "running";
@@ -2915,17 +3014,47 @@ function inferLoopStatusFromTurns(turns, messages) {
2915
3014
  }
2916
3015
  return inferLoopStatusFromMessages(messages);
2917
3016
  }
2918
- function isRecord4(value) {
3017
+ function isRecord5(value) {
2919
3018
  return typeof value === "object" && value !== null && !Array.isArray(value);
2920
3019
  }
3020
+ function toSelectionMap(value) {
3021
+ if (!isRecord5(value)) return {};
3022
+ const entries = Object.entries(value).map(([questionKey, optionIndexes]) => {
3023
+ if (!Array.isArray(optionIndexes)) return null;
3024
+ const parsedIndexes = optionIndexes.map((item) => typeof item === "number" ? item : Number(item)).filter((item) => Number.isInteger(item));
3025
+ return [Number(questionKey), parsedIndexes];
3026
+ }).filter((entry) => entry !== null);
3027
+ return Object.fromEntries(entries);
3028
+ }
3029
+ function toCustomMap(value) {
3030
+ if (!isRecord5(value)) return {};
3031
+ const entries = Object.entries(value).filter(([, text]) => typeof text === "string").map(([questionKey, text]) => [Number(questionKey), text]);
3032
+ return Object.fromEntries(entries);
3033
+ }
3034
+ function extractAskAnswers(turns) {
3035
+ const answers = {};
3036
+ for (const turn of turns) {
3037
+ for (const block of turn.blocks) {
3038
+ if (block.type !== "ask_user_answer" || typeof block.tool_call_id !== "string") continue;
3039
+ if (!isRecord5(block.content)) continue;
3040
+ const note = typeof block.content.note === "string" ? block.content.note.trim() : "";
3041
+ answers[block.tool_call_id] = {
3042
+ selections: toSelectionMap(block.content.selections),
3043
+ custom: toCustomMap(block.content.custom),
3044
+ ...note ? { note } : {}
3045
+ };
3046
+ }
3047
+ }
3048
+ return answers;
3049
+ }
2921
3050
  function parentForkToolCallIdFromTurn(turn) {
2922
3051
  if (typeof turn.parent_fork_tool_call_id === "string" && turn.parent_fork_tool_call_id.length > 0) {
2923
3052
  return turn.parent_fork_tool_call_id;
2924
3053
  }
2925
3054
  for (const block of turn.blocks) {
2926
- if (block.type !== "system_notification" || !isRecord4(block.content)) continue;
3055
+ if (block.type !== "system_notification" || !isRecord5(block.content)) continue;
2927
3056
  const metadata = block.content.metadata;
2928
- if (!isRecord4(metadata)) continue;
3057
+ if (!isRecord5(metadata)) continue;
2929
3058
  const parentId = metadata.parent_fork_tool_call_id;
2930
3059
  if (typeof parentId === "string" && parentId.length > 0) return parentId;
2931
3060
  }
@@ -2942,16 +3071,16 @@ function buildMessageContent2(turn) {
2942
3071
  }
2943
3072
  function workspaceNotificationContent(turn) {
2944
3073
  const block = turn.blocks.find(
2945
- (candidate) => candidate.type === "system_notification" && isRecord4(candidate.content) && candidate.content.notification_type === "workspace_change"
3074
+ (candidate) => candidate.type === "system_notification" && isRecord5(candidate.content) && candidate.content.notification_type === "workspace_change"
2946
3075
  );
2947
- if (!block || !isRecord4(block.content)) return "";
3076
+ if (!block || !isRecord5(block.content)) return "";
2948
3077
  const title = typeof block.content.title === "string" ? block.content.title.trim() : "";
2949
3078
  const detail = typeof block.content.detail === "string" ? block.content.detail.trim() : "";
2950
3079
  return [title ? `**${title}**` : "", detail].filter(Boolean).join("\n\n");
2951
3080
  }
2952
3081
  function askUserAnswerContent(turn) {
2953
3082
  const answerBlock = turn.blocks.find((block) => block.type === "ask_user_answer");
2954
- if (!answerBlock || !isRecord4(answerBlock.content)) return null;
3083
+ if (!answerBlock || !isRecord5(answerBlock.content)) return null;
2955
3084
  const answer = answerBlock.content.answer;
2956
3085
  return typeof answer === "string" && answer.trim().length > 0 ? answer : null;
2957
3086
  }
@@ -2971,6 +3100,19 @@ function extractModeFromBlocks(blocks) {
2971
3100
  return null;
2972
3101
  }
2973
3102
  function projectionToMessage(turn) {
3103
+ if (turn.kind === "context") {
3104
+ const context = contextProjectionData(turn);
3105
+ if (!context) return null;
3106
+ return {
3107
+ role: "assistant",
3108
+ content: "",
3109
+ kind: "context",
3110
+ context,
3111
+ loop_name: turn.loop_id,
3112
+ entry_id: turn.turn_id,
3113
+ status: turn.status
3114
+ };
3115
+ }
2974
3116
  if (turn.kind === "compaction" && turn.compaction_id) {
2975
3117
  return {
2976
3118
  role: "assistant",
@@ -3140,6 +3282,25 @@ function orderTurns(turns) {
3140
3282
  }
3141
3283
  return turns;
3142
3284
  }
3285
+ function reconcileOptimisticUserTurns(currentTurns, incomingTurns) {
3286
+ const currentById = new Map(currentTurns.map((turn) => [turn.turn_id, turn]));
3287
+ const optimisticUserTurn = currentTurns.find(
3288
+ (turn) => turn.role === "user" && (turn.kind ?? "message") === "message" && turn.turn_id.startsWith("local-user-")
3289
+ );
3290
+ let optimisticUserMatched = false;
3291
+ return incomingTurns.map((turn) => {
3292
+ const current = currentById.get(turn.turn_id);
3293
+ if (current) {
3294
+ const currentRenderId = current.client_render_id;
3295
+ return turn.client_render_id || !currentRenderId ? turn : { ...turn, client_render_id: currentRenderId };
3296
+ }
3297
+ if (!optimisticUserMatched && optimisticUserTurn && turn.role === "user" && (turn.kind ?? "message") === "message" && !turn.turn_id.startsWith("local-user-")) {
3298
+ optimisticUserMatched = true;
3299
+ return { ...turn, client_render_id: optimisticUserTurn.turn_id };
3300
+ }
3301
+ return turn;
3302
+ });
3303
+ }
3143
3304
  function areAgentLoopsEqual(left, right) {
3144
3305
  if (!left) return Object.keys(right).length === 0;
3145
3306
  const leftEntries = Object.entries(left);
@@ -3151,7 +3312,7 @@ function areAgentLoopsEqual(left, right) {
3151
3312
  });
3152
3313
  }
3153
3314
  function withTurns(state, turns) {
3154
- const orderedTurns = orderTurns(turns);
3315
+ const orderedTurns = orderTurns(reconcileOptimisticUserTurns(state.turns, turns));
3155
3316
  const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
3156
3317
  const isWaitingForInput = orderedTurns.some(
3157
3318
  (turn) => turn.tool_calls.some((toolCall) => toolCall.status === "awaiting_answer")
@@ -3161,14 +3322,31 @@ function withTurns(state, turns) {
3161
3322
  const preservedErrors = lastTurnId ? state.messages.filter(
3162
3323
  (m) => m.role === "error" && typeof m.entry_id === "string" && m.entry_id.startsWith(`${ERROR_ANCHOR_PREFIX}${lastTurnId}:`)
3163
3324
  ) : [];
3164
- const mergedMessages = preservedErrors.length > 0 ? [...messages, ...preservedErrors] : messages;
3325
+ const turnById = new Map(orderedTurns.map((turn) => [turn.turn_id, turn]));
3326
+ const messagesWithRenderIds = messages.map((message) => {
3327
+ if (!message.entry_id) return message;
3328
+ const renderId = turnById.get(message.entry_id)?.client_render_id;
3329
+ return renderId ? { ...message, render_id: renderId } : message;
3330
+ });
3331
+ const mergedMessages = preservedErrors.length > 0 ? [...messagesWithRenderIds, ...preservedErrors] : messagesWithRenderIds;
3165
3332
  const latestMode = [...orderedTurns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
3333
+ const askAnswersFromHistory = extractAskAnswers(orderedTurns);
3334
+ const presentToolCallIds = new Set(
3335
+ orderedTurns.flatMap((turn) => turn.tool_calls.map((toolCall) => toolCall.id))
3336
+ );
3337
+ const askAnswers = { ...askAnswersFromHistory };
3338
+ for (const [toolCallId, answer] of Object.entries(state.askAnswers)) {
3339
+ if (!(toolCallId in askAnswers) && presentToolCallIds.has(toolCallId)) {
3340
+ askAnswers[toolCallId] = answer;
3341
+ }
3342
+ }
3166
3343
  return {
3167
3344
  ...state,
3168
3345
  turns: orderedTurns,
3169
3346
  messages: mergedMessages,
3170
3347
  agentLoops: stableAgentLoops,
3171
3348
  activeCompaction,
3349
+ askAnswers,
3172
3350
  mode: latestMode ?? state.mode,
3173
3351
  // 历史恢复时 chat:end(paused) 可能已不在 socket replay 缓存中;
3174
3352
  // awaiting_answer 是投影里的持久权威状态,必须据此恢复问答交互。
@@ -3196,14 +3374,17 @@ function addUserMessage(state, content) {
3196
3374
  }
3197
3375
  function upsertTurn(state, turn) {
3198
3376
  let existing = [...state.turns];
3199
- if (turn.role === "user" && !turn.turn_id.startsWith("local-user-")) {
3200
- existing = existing.filter((t) => !t.turn_id.startsWith("local-user-"));
3377
+ const [nextTurn] = reconcileOptimisticUserTurns(existing, [turn]);
3378
+ if (nextTurn.client_render_id && turn.role === "user") {
3379
+ existing = existing.filter(
3380
+ (item) => !(item.role === "user" && item.turn_id.startsWith("local-user-"))
3381
+ );
3201
3382
  }
3202
- const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
3383
+ const index = existing.findIndex((item) => item.turn_id === nextTurn.turn_id);
3203
3384
  if (index >= 0) {
3204
- existing[index] = turn;
3385
+ existing[index] = nextTurn;
3205
3386
  } else {
3206
- existing.push(turn);
3387
+ existing.push(nextTurn);
3207
3388
  }
3208
3389
  return withTurns(state, existing);
3209
3390
  }
@@ -3255,18 +3436,18 @@ function markStreamingTurns(state, turnStatus, toolCallStatus) {
3255
3436
  // src/session/agent-session.ts
3256
3437
  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
3438
  var OOM_KEYWORDS_RE = /(?:\b(?:exit_code|ExitedWith|exited with)|退出码)\D*(?<!\d)137(?!\d)/i;
3258
- function isRecord5(value) {
3439
+ function isRecord6(value) {
3259
3440
  return typeof value === "object" && value !== null && !Array.isArray(value);
3260
3441
  }
3261
3442
  function isOomText(value) {
3262
3443
  return typeof value === "string" && OOM_KEYWORDS_RE.test(value);
3263
3444
  }
3264
3445
  function parseJsonRecord(value) {
3265
- if (isRecord5(value)) return value;
3446
+ if (isRecord6(value)) return value;
3266
3447
  if (typeof value !== "string") return null;
3267
3448
  try {
3268
3449
  const parsed = JSON.parse(value);
3269
- return isRecord5(parsed) ? parsed : null;
3450
+ return isRecord6(parsed) ? parsed : null;
3270
3451
  } catch {
3271
3452
  return null;
3272
3453
  }
@@ -3313,7 +3494,7 @@ function hasChatRunEvent2(events) {
3313
3494
  );
3314
3495
  }
3315
3496
  function isUiMetaLike(value) {
3316
- return isRecord5(value) && ("resourceHTML" in value || "resourceUri" in value || "resourceURI" in value);
3497
+ return isRecord6(value) && ("resourceHTML" in value || "resourceUri" in value || "resourceURI" in value);
3317
3498
  }
3318
3499
  var AgentSession = class _AgentSession {
3319
3500
  sessionId;
@@ -3464,7 +3645,7 @@ var AgentSession = class _AgentSession {
3464
3645
  if (this.getAppContext) {
3465
3646
  try {
3466
3647
  const rawContext = await this.getAppContext();
3467
- if (!isRecord5(rawContext)) {
3648
+ if (!isRecord6(rawContext)) {
3468
3649
  throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE JSON \u5BF9\u8C61");
3469
3650
  }
3470
3651
  const serialized = JSON.stringify(rawContext);
@@ -3472,7 +3653,7 @@ var AgentSession = class _AgentSession {
3472
3653
  throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE\u53EF\u5E8F\u5217\u5316\u7684 JSON \u5BF9\u8C61");
3473
3654
  }
3474
3655
  const normalized = JSON.parse(serialized);
3475
- if (!isRecord5(normalized)) {
3656
+ if (!isRecord6(normalized)) {
3476
3657
  throw new Error("getContext \u5FC5\u987B\u8FD4\u56DE JSON \u5BF9\u8C61");
3477
3658
  }
3478
3659
  appContext = normalized;
@@ -3486,13 +3667,11 @@ var AgentSession = class _AgentSession {
3486
3667
  let optimisticTurnId = null;
3487
3668
  let optimisticAskAnswer;
3488
3669
  let previousAskAnswer;
3489
- if (!(askUserAnswer && typeof askUserAnswer.tool_call_id === "string")) {
3490
- this.update((state) => {
3491
- const next = addUserMessage(state, content);
3492
- optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
3493
- return next;
3494
- });
3495
- }
3670
+ this.update((state) => {
3671
+ const next = addUserMessage(state, content);
3672
+ optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
3673
+ return next;
3674
+ });
3496
3675
  this.update((s) => ({ ...s, isStreaming: true }));
3497
3676
  if (askUserAnswer && typeof askUserAnswer.tool_call_id === "string") {
3498
3677
  const { tool_call_id, ...rest } = askUserAnswer;
@@ -3516,7 +3695,8 @@ var AgentSession = class _AgentSession {
3516
3695
  thinking_override: options.thinkingOverride,
3517
3696
  whatif: options.whatif,
3518
3697
  replay_decision: options.replayDecision,
3519
- app_context: appContext
3698
+ app_context: appContext,
3699
+ kb_ids: options.kbIds && options.kbIds.length > 0 ? options.kbIds : void 0
3520
3700
  };
3521
3701
  try {
3522
3702
  await this.ensureJoined();
@@ -3855,9 +4035,9 @@ var AgentSession = class _AgentSession {
3855
4035
  detail,
3856
4036
  status,
3857
4037
  loopId,
3858
- metadata: isRecord5(notification.metadata) ? notification.metadata : void 0
4038
+ metadata: isRecord6(notification.metadata) ? notification.metadata : void 0
3859
4039
  });
3860
- if (notificationType === "bg:started" && isRecord5(notification.metadata)) {
4040
+ if (notificationType === "bg:started" && isRecord6(notification.metadata)) {
3861
4041
  const taskId = typeof notification.metadata.task_id === "string" ? notification.metadata.task_id : "";
3862
4042
  if (taskId) {
3863
4043
  this.emitter.emit("backgroundTask", {
@@ -3987,7 +4167,7 @@ var AgentSession = class _AgentSession {
3987
4167
  title: ui.title ?? "\u5DE5\u5177\u9884\u89C8"
3988
4168
  });
3989
4169
  }
3990
- if (block.type === "system_notification" && isRecord5(block.content)) {
4170
+ if (block.type === "system_notification" && isRecord6(block.content)) {
3991
4171
  this._handleSystemNotification(turn.loop_id, block.content);
3992
4172
  }
3993
4173
  }
@@ -4471,7 +4651,7 @@ function resolveAuthToken(options) {
4471
4651
 
4472
4652
  // src/version.ts
4473
4653
  var SDK_NAME = "agent-client";
4474
- var SDK_VERSION = true ? "2610.0.0-beta.2" : "1.1.1";
4654
+ var SDK_VERSION = true ? "2610.0.0-beta.21" : "1.1.1";
4475
4655
 
4476
4656
  // src/socket.ts
4477
4657
  function withSdkIdentity(auth) {
@@ -4864,6 +5044,89 @@ function parseXhrHeaders(rawHeaders) {
4864
5044
  return headers;
4865
5045
  }
4866
5046
 
5047
+ // src/platform-endpoints.ts
5048
+ var PLATFORM_SERVICE_NAMES = [
5049
+ "os",
5050
+ "hub",
5051
+ "agent",
5052
+ "oauth",
5053
+ "llmGateway",
5054
+ "gitea",
5055
+ "tile"
5056
+ ];
5057
+ var EMPTY_PLATFORM_ENDPOINTS = Object.freeze({
5058
+ services: Object.freeze({})
5059
+ });
5060
+ function defaultDocumentBaseUrl() {
5061
+ if (typeof document === "undefined") return void 0;
5062
+ if (document.querySelector("base[href]")) return document.baseURI;
5063
+ if (typeof location !== "undefined" && location.origin) return `${location.origin}/`;
5064
+ return document.baseURI;
5065
+ }
5066
+ function isRecord7(value) {
5067
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5068
+ }
5069
+ function normalizeServiceUrl(value) {
5070
+ if (typeof value !== "string" || !value.trim()) return null;
5071
+ if (value.includes("?") || value.includes("#")) return null;
5072
+ try {
5073
+ const url = new URL(value.trim());
5074
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
5075
+ if (url.username || url.password || url.search || url.hash) return null;
5076
+ return url.toString().replace(/\/$/, "");
5077
+ } catch {
5078
+ return null;
5079
+ }
5080
+ }
5081
+ function parsePlatformEndpoints(value) {
5082
+ if (!isRecord7(value) || !isRecord7(value.services)) return null;
5083
+ const services = {};
5084
+ for (const name of PLATFORM_SERVICE_NAMES) {
5085
+ if (!(name in value.services)) continue;
5086
+ const url = normalizeServiceUrl(value.services[name]);
5087
+ if (!url) return null;
5088
+ if (name === "agent" && new URL(url).pathname !== "/") return null;
5089
+ services[name] = url;
5090
+ }
5091
+ return { services };
5092
+ }
5093
+ async function loadPlatformEndpoints(options = {}) {
5094
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
5095
+ const baseUrl = options.baseUrl ?? defaultDocumentBaseUrl();
5096
+ if (!fetchImpl || !baseUrl) return EMPTY_PLATFORM_ENDPOINTS;
5097
+ const controller = new AbortController();
5098
+ const timeout = globalThis.setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
5099
+ try {
5100
+ const response = await fetchImpl(new URL("config.json", baseUrl), {
5101
+ cache: "no-store",
5102
+ credentials: "same-origin",
5103
+ headers: { Accept: "application/json" },
5104
+ signal: controller.signal
5105
+ });
5106
+ if (!response.ok) return EMPTY_PLATFORM_ENDPOINTS;
5107
+ return parsePlatformEndpoints(await response.json()) ?? EMPTY_PLATFORM_ENDPOINTS;
5108
+ } catch {
5109
+ return EMPTY_PLATFORM_ENDPOINTS;
5110
+ } finally {
5111
+ globalThis.clearTimeout(timeout);
5112
+ }
5113
+ }
5114
+ function resolveServiceUrl(endpoints, name, path = "") {
5115
+ const baseUrl = endpoints.services[name];
5116
+ if (!baseUrl) return null;
5117
+ if (!path) return baseUrl;
5118
+ const pathReference = path.trimStart();
5119
+ if (path.includes("\\") || pathReference.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(pathReference)) {
5120
+ return null;
5121
+ }
5122
+ const base = new URL(`${baseUrl}/`);
5123
+ const target = new URL(path.replace(/^\/+/, ""), base);
5124
+ if (target.origin !== base.origin || !target.pathname.startsWith(base.pathname)) {
5125
+ return null;
5126
+ }
5127
+ return target.toString();
5128
+ }
5129
+
4867
5130
  // src/commands/protocol.ts
4868
5131
  function isCommandEnvelope(value) {
4869
5132
  if (typeof value !== "object" || value === null) return false;
@@ -4968,6 +5231,7 @@ export {
4968
5231
  BladeClient,
4969
5232
  ClientProjectionBuilder,
4970
5233
  DEFAULT_REPLAY_SPEED,
5234
+ EMPTY_PLATFORM_ENDPOINTS,
4971
5235
  LayoutType,
4972
5236
  ModelsResource,
4973
5237
  SDK_NAME,
@@ -4982,9 +5246,11 @@ export {
4982
5246
  buildMessageContent,
4983
5247
  connectEmbedded,
4984
5248
  contentPreview,
5249
+ contextProjectionData,
4985
5250
  createInitialSessionState,
4986
5251
  createSocket,
4987
5252
  extractTextAttachments,
5253
+ getContextDisplayState,
4988
5254
  getFileParts,
4989
5255
  getImageParts,
4990
5256
  getTextContent,
@@ -4993,7 +5259,10 @@ export {
4993
5259
  isHiddenInternalMessage,
4994
5260
  isInboundEnvelope,
4995
5261
  latestPostChatFollowup,
5262
+ loadPlatformEndpoints,
4996
5263
  normalizeMessageContent,
5264
+ reconcileOptimisticUserTurns,
5265
+ resolveServiceUrl,
4997
5266
  toReplaySnapshot,
4998
5267
  transformSlashCommand
4999
5268
  };