@truefoundry/assistant-ui-runtime 0.1.0-rc.1 → 0.1.1

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.
Files changed (30) hide show
  1. package/README.md +14 -77
  2. package/dist/index.d.ts +4 -1
  3. package/dist/index.js +314 -122
  4. package/dist/index.js.map +1 -1
  5. package/package.json +2 -2
  6. package/src/agentSessionModule.d.ts +14 -2
  7. package/src/convertTurnMessages.test.ts +362 -0
  8. package/src/convertTurnMessages.ts +193 -28
  9. package/src/createSubAgent.ts +13 -5
  10. package/src/draftAgentConfig.test.ts +1 -1
  11. package/src/foldPeerThreads.test.ts +56 -0
  12. package/src/foldPeerThreads.ts +7 -1
  13. package/src/hooks.ts +6 -0
  14. package/src/index.ts +6 -5
  15. package/src/loadSessionSnapshot.test.ts +21 -6
  16. package/src/loadSessionSnapshot.ts +9 -5
  17. package/src/{truefoundryDraftThreadListAdapter.ts → private/truefoundryDraftThreadListAdapter.ts} +1 -1
  18. package/src/sessionSnapshot.ts +27 -1
  19. package/src/sessions.ts +1 -1
  20. package/src/truefoundryExtras.ts +2 -1
  21. package/src/types.ts +1 -1
  22. package/src/useTrueFoundryAgentMessages.test.tsx +225 -1
  23. package/src/useTrueFoundryAgentMessages.ts +178 -60
  24. package/src/useTrueFoundryAgentRuntime.ts +27 -13
  25. /package/src/{agentSpec.ts → private/agentSpec.ts} +0 -0
  26. /package/src/{bindDraftAgentSession.test.ts → private/bindDraftAgentSession.test.ts} +0 -0
  27. /package/src/{bindDraftAgentSession.ts → private/bindDraftAgentSession.ts} +0 -0
  28. /package/src/{draftSessionBridge.ts → private/draftSessionBridge.ts} +0 -0
  29. /package/src/{truefoundryDraftThreadListAdapter.test.ts → private/truefoundryDraftThreadListAdapter.test.ts} +0 -0
  30. /package/src/{useDraftAgentSpec.ts → private/useDraftAgentSpec.ts} +0 -0
package/dist/index.js CHANGED
@@ -276,7 +276,10 @@ function parseAskUserQuestionArgs(argsText) {
276
276
 
277
277
  // src/createSubAgent.ts
278
278
  function isCreateSubAgentToolCall(toolCall) {
279
- return toolCall.toolInfo.type === "truefoundry-system" && toolCall.toolInfo.name === "create_sub_agent";
279
+ if (toolCall.toolInfo?.type === "truefoundry-system" && toolCall.toolInfo?.name === "create_sub_agent") {
280
+ return true;
281
+ }
282
+ return toolCall.function?.name === "create_sub_agent";
280
283
  }
281
284
 
282
285
  // src/modelMessageImageContent.ts
@@ -735,10 +738,13 @@ function attachSubAgentMessages(state, parentThreadId, parts) {
735
738
  });
736
739
  }
737
740
  }
738
- if (messages.length === 0) {
741
+ if (messages.length === 0 && subAgents.length === 0) {
739
742
  return part;
740
743
  }
741
744
  const artifact = { subAgents };
745
+ if (messages.length === 0) {
746
+ return { ...part, artifact };
747
+ }
742
748
  return { ...part, messages, artifact };
743
749
  });
744
750
  }
@@ -1144,6 +1150,27 @@ function deriveSandboxId(messages) {
1144
1150
  return void 0;
1145
1151
  }
1146
1152
 
1153
+ // src/extractTurnUserText.ts
1154
+ function extractTurnUserText(input) {
1155
+ const parts = [];
1156
+ for (const item of input ?? []) {
1157
+ if (item.type !== "user.message") {
1158
+ continue;
1159
+ }
1160
+ const { content } = item;
1161
+ if (typeof content === "string") {
1162
+ parts.push(content);
1163
+ continue;
1164
+ }
1165
+ for (const part of content) {
1166
+ if (part.type === "text") {
1167
+ parts.push(part.text);
1168
+ }
1169
+ }
1170
+ }
1171
+ return parts.join("\n").trim();
1172
+ }
1173
+
1147
1174
  // src/mcpAuth.ts
1148
1175
  var MCP_AUTH_RESUME_RUN_CUSTOM_KEY = "resumeMcpAuth";
1149
1176
  function buildMcpAuthTextParts(servers) {
@@ -1230,27 +1257,6 @@ function appendMcpAuthToTurnContent(content, turn) {
1230
1257
  );
1231
1258
  }
1232
1259
 
1233
- // src/extractTurnUserText.ts
1234
- function extractTurnUserText(input) {
1235
- const parts = [];
1236
- for (const item of input ?? []) {
1237
- if (item.type !== "user.message") {
1238
- continue;
1239
- }
1240
- const { content } = item;
1241
- if (typeof content === "string") {
1242
- parts.push(content);
1243
- continue;
1244
- }
1245
- for (const part of content) {
1246
- if (part.type === "text") {
1247
- parts.push(part.text);
1248
- }
1249
- }
1250
- }
1251
- return parts.join("\n").trim();
1252
- }
1253
-
1254
1260
  // src/sessionSnapshot.ts
1255
1261
  function emptyRequiredActionsOverlay() {
1256
1262
  return {
@@ -1275,6 +1281,18 @@ function turnToSessionRecord(turn) {
1275
1281
  input: turn.input
1276
1282
  };
1277
1283
  }
1284
+ function sessionEventsToSessionRecord(turnId, createdEvent, doneEvent, rootModelMessageIds, sandboxId) {
1285
+ const userText = extractTurnUserText(createdEvent.input);
1286
+ return {
1287
+ id: turnId,
1288
+ ...userText ? { userText } : {},
1289
+ createdAt: createdEvent.createdAt,
1290
+ state: doneEvent.state,
1291
+ input: createdEvent.input,
1292
+ rootModelMessageIds,
1293
+ ...sandboxId != null ? { sandboxId } : {}
1294
+ };
1295
+ }
1278
1296
  function replaceSessionSnapshot(snapshot, patch) {
1279
1297
  return {
1280
1298
  ...snapshot,
@@ -1285,6 +1303,7 @@ function replaceSessionSnapshot(snapshot, patch) {
1285
1303
 
1286
1304
  // src/convertTurnMessages.ts
1287
1305
  var TURN_EVENTS_PAGE_SIZE = 25;
1306
+ var SESSION_EVENTS_PAGE_SIZE = 100;
1288
1307
  function assistantStatusFromTurnState(state) {
1289
1308
  switch (state.status) {
1290
1309
  case "done":
@@ -1691,6 +1710,92 @@ function projectSessionMessages(snapshot, options) {
1691
1710
  );
1692
1711
  }
1693
1712
  var DEFAULT_LIST_EVENTS_CONCURRENCY = 5;
1713
+ async function fetchAllSessionEvents(session, options) {
1714
+ const items = [];
1715
+ for await (const item of await session.listEvents({
1716
+ limit: SESSION_EVENTS_PAGE_SIZE,
1717
+ ...options?.lastTurnId != null ? { lastTurnId: options.lastTurnId } : {}
1718
+ })) {
1719
+ items.push(item);
1720
+ }
1721
+ items.reverse();
1722
+ return items;
1723
+ }
1724
+ function ingestSessionEventsIntoSnapshot(snapshot, items, onTurnComplete) {
1725
+ let currentTurnId = null;
1726
+ let currentCreatedEvent = null;
1727
+ let currentContentEvents = [];
1728
+ let beforeCount = 0;
1729
+ for (const item of items) {
1730
+ const { turnId, event } = item;
1731
+ if (event.type === "turn.created") {
1732
+ currentTurnId = turnId;
1733
+ currentCreatedEvent = event;
1734
+ currentContentEvents = [];
1735
+ beforeCount = snapshot.fold.threads.get(ROOT_THREAD_ID)?.modelMessageIds.length ?? 0;
1736
+ } else if (event.type === "turn.done") {
1737
+ if (currentTurnId == null || currentCreatedEvent == null) {
1738
+ continue;
1739
+ }
1740
+ const afterBucket = snapshot.fold.threads.get(ROOT_THREAD_ID);
1741
+ const rootModelMessageIds = (afterBucket?.modelMessageIds ?? []).slice(
1742
+ beforeCount
1743
+ );
1744
+ const sandboxEvent = currentContentEvents.find(
1745
+ (ev) => ev.type === "sandbox.created"
1746
+ );
1747
+ applyUserToolResponsesToFold(
1748
+ snapshot.fold,
1749
+ currentCreatedEvent.input ?? []
1750
+ );
1751
+ snapshot.turns.push(
1752
+ sessionEventsToSessionRecord(
1753
+ currentTurnId,
1754
+ currentCreatedEvent,
1755
+ event,
1756
+ rootModelMessageIds,
1757
+ sandboxEvent?.sandboxId
1758
+ )
1759
+ );
1760
+ onTurnComplete?.(replaceSessionSnapshot(snapshot, {}));
1761
+ currentTurnId = null;
1762
+ currentCreatedEvent = null;
1763
+ currentContentEvents = [];
1764
+ } else {
1765
+ if (currentTurnId != null) {
1766
+ ingestTurnEvent(snapshot.fold, event);
1767
+ currentContentEvents.push(event);
1768
+ }
1769
+ }
1770
+ }
1771
+ }
1772
+ async function buildSnapshotFromSessionEvents(session, onProgress) {
1773
+ let runningTurn;
1774
+ for await (const turn of await session.listTurns({ limit: 1 })) {
1775
+ if (turn.state?.status === "running") {
1776
+ runningTurn = turn;
1777
+ }
1778
+ }
1779
+ const items = await fetchAllSessionEvents(session);
1780
+ const snapshot = createEmptySessionSnapshot();
1781
+ ingestSessionEventsIntoSnapshot(snapshot, items, onProgress);
1782
+ if (runningTurn == null) {
1783
+ return snapshot;
1784
+ }
1785
+ const pendingUserText = extractTurnUserText(runningTurn.input);
1786
+ return replaceSessionSnapshot(snapshot, {
1787
+ runningTurn,
1788
+ unstable_resume: true,
1789
+ groupRootBaseline: computeGroupRootBaseline(snapshot.turns),
1790
+ ...pendingUserText ? {
1791
+ pendingUser: {
1792
+ turnId: runningTurn.id,
1793
+ content: extractTurnUserMessageContent(runningTurn.input),
1794
+ createdAt: new Date(runningTurn.createdAt)
1795
+ }
1796
+ } : {}
1797
+ });
1798
+ }
1694
1799
  function ingestTurnsIntoSnapshot(snapshot, turns, eventArrays) {
1695
1800
  let runningTurn;
1696
1801
  for (let i = 0; i < turns.length; i++) {
@@ -1719,37 +1824,32 @@ function ingestTurnsIntoSnapshot(snapshot, turns, eventArrays) {
1719
1824
  return runningTurn;
1720
1825
  }
1721
1826
  async function buildSnapshotFromSession(session, concurrency = DEFAULT_LIST_EVENTS_CONCURRENCY) {
1722
- const turns = [];
1723
- for await (const turn of await session.listTurns()) {
1724
- turns.push(turn);
1827
+ const snapshot = await buildSnapshotFromSessionEvents(session);
1828
+ if (snapshot.runningTurn == null) {
1829
+ return snapshot;
1725
1830
  }
1726
- turns.reverse();
1727
- const eventArrays = await fetchAllTurnEventsWithConcurrency(turns, concurrency);
1728
- const snapshot = createEmptySessionSnapshot();
1729
- const runningTurn = ingestTurnsIntoSnapshot(snapshot, turns, eventArrays);
1831
+ const turn = snapshot.runningTurn;
1832
+ const eventArrays = await fetchAllTurnEventsWithConcurrency([turn], concurrency);
1833
+ ingestTurnsIntoSnapshot(snapshot, [turn], eventArrays);
1730
1834
  return replaceSessionSnapshot(snapshot, {
1731
- ...runningTurn != null ? {
1732
- runningTurn,
1733
- unstable_resume: true,
1734
- groupRootBaseline: computeGroupRootBaseline(snapshot.turns)
1735
- } : {}
1835
+ runningTurn: turn,
1836
+ unstable_resume: true,
1837
+ groupRootBaseline: computeGroupRootBaseline(snapshot.turns)
1736
1838
  });
1737
1839
  }
1738
- async function buildSnapshotBeforeTurnIndex(session, turnIndex, concurrency = DEFAULT_LIST_EVENTS_CONCURRENCY, orderedTurns) {
1840
+ async function buildSnapshotBeforeTurnIndex(session, turnIndex, _concurrency = DEFAULT_LIST_EVENTS_CONCURRENCY, orderedTurns) {
1739
1841
  if (turnIndex <= 0) {
1740
1842
  return createEmptySessionSnapshot();
1741
1843
  }
1742
1844
  const turns = orderedTurns ?? await listSessionTurnsOrdered(session);
1743
1845
  const turnsToInclude = turns.slice(0, turnIndex);
1744
- if (turnsToInclude.length === 0) {
1846
+ const lastTurnId = turnsToInclude.at(-1)?.id;
1847
+ if (lastTurnId == null) {
1745
1848
  return createEmptySessionSnapshot();
1746
1849
  }
1747
- const eventArrays = await fetchAllTurnEventsWithConcurrency(
1748
- turnsToInclude,
1749
- concurrency
1750
- );
1850
+ const items = await fetchAllSessionEvents(session, { lastTurnId });
1751
1851
  const snapshot = createEmptySessionSnapshot();
1752
- ingestTurnsIntoSnapshot(snapshot, turnsToInclude, eventArrays);
1852
+ ingestSessionEventsIntoSnapshot(snapshot, items);
1753
1853
  return snapshot;
1754
1854
  }
1755
1855
  async function resolveGatewayBranchPreviousTurnId(session, turnIndex, orderedTurns) {
@@ -2024,7 +2124,7 @@ function repositoryItemsFromMessages(messages) {
2024
2124
  return items;
2025
2125
  }
2026
2126
 
2027
- // src/draftSessionBridge.ts
2127
+ // src/private/draftSessionBridge.ts
2028
2128
  function createDraftSessionBridge(gateway) {
2029
2129
  async function getDraft(draftSessionId) {
2030
2130
  const response = await gateway.agents.private.draftSessions.get(draftSessionId);
@@ -2041,7 +2141,7 @@ function createDraftSessionBridge(gateway) {
2041
2141
  };
2042
2142
  }
2043
2143
 
2044
- // src/agentSpec.ts
2144
+ // src/private/agentSpec.ts
2045
2145
  function mergeAgentSpec(base, update) {
2046
2146
  const { model: modelUpdate, ...rest } = update;
2047
2147
  const next = {
@@ -2069,7 +2169,7 @@ function sessionListStartTimestamp() {
2069
2169
  return start.toISOString();
2070
2170
  }
2071
2171
 
2072
- // src/truefoundryDraftThreadListAdapter.ts
2172
+ // src/private/truefoundryDraftThreadListAdapter.ts
2073
2173
  var THREAD_LIST_PAGE_SIZE = 20;
2074
2174
  function createTrueFoundryDraftThreadListAdapter(options) {
2075
2175
  const { gateway, defaultAgentSpec, getAgentSpec } = options;
@@ -2137,7 +2237,7 @@ var EMPTY_DRAFT_EXTRAS = {
2137
2237
  }
2138
2238
  };
2139
2239
 
2140
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/core/fetcher/HttpResponsePromise.mjs
2240
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/core/fetcher/HttpResponsePromise.mjs
2141
2241
  var __awaiter = function(thisArg, _arguments, P, generator) {
2142
2242
  function adopt(value) {
2143
2243
  return value instanceof P ? value : new P(function(resolve) {
@@ -2252,7 +2352,7 @@ var HttpResponsePromise = class _HttpResponsePromise extends Promise {
2252
2352
  }
2253
2353
  };
2254
2354
 
2255
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/core/pagination/Page.mjs
2355
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/core/pagination/Page.mjs
2256
2356
  var __awaiter2 = function(thisArg, _arguments, P, generator) {
2257
2357
  function adopt(value) {
2258
2358
  return value instanceof P ? value : new P(function(resolve) {
@@ -2407,7 +2507,7 @@ var Page = class {
2407
2507
  }
2408
2508
  };
2409
2509
 
2410
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/agents/TurnStreamData.mjs
2510
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/agents/TurnStreamData.mjs
2411
2511
  function parseSequenceNumber(id) {
2412
2512
  if (!id) {
2413
2513
  throw new Error("Missing SSE sequence number id.");
@@ -2419,7 +2519,7 @@ function parseSequenceNumber(id) {
2419
2519
  return parsed;
2420
2520
  }
2421
2521
 
2422
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/agents/Turn.mjs
2522
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/agents/Turn.mjs
2423
2523
  var __awaiter3 = function(thisArg, _arguments, P, generator) {
2424
2524
  function adopt(value) {
2425
2525
  return value instanceof P ? value : new P(function(resolve) {
@@ -2657,7 +2757,7 @@ _Turn_client = /* @__PURE__ */ new WeakMap(), _Turn_state = /* @__PURE__ */ new
2657
2757
  __classPrivateFieldSet(this, _Turn_state, event.state, "f");
2658
2758
  };
2659
2759
 
2660
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/agents/PreparedTurn.mjs
2760
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/agents/PreparedTurn.mjs
2661
2761
  var __awaiter4 = function(thisArg, _arguments, P, generator) {
2662
2762
  function adopt(value) {
2663
2763
  return value instanceof P ? value : new P(function(resolve) {
@@ -3012,7 +3112,7 @@ var PreparedTurn = class {
3012
3112
  };
3013
3113
  _PreparedTurn_client = /* @__PURE__ */ new WeakMap(), _PreparedTurn_input = /* @__PURE__ */ new WeakMap(), _PreparedTurn_previousTurnIdInput = /* @__PURE__ */ new WeakMap(), _PreparedTurn_start = /* @__PURE__ */ new WeakMap(), _PreparedTurn_turn = /* @__PURE__ */ new WeakMap();
3014
3114
 
3015
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/agents/AgentSession.mjs
3115
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/agents/AgentSession.mjs
3016
3116
  var __awaiter5 = function(thisArg, _arguments, P, generator) {
3017
3117
  function adopt(value) {
3018
3118
  return value instanceof P ? value : new P(function(resolve) {
@@ -3122,10 +3222,22 @@ var AgentSession = class {
3122
3222
  yield __classPrivateFieldGet3(this, _AgentSession_client, "f").agents.sessions.cancel(this.id, {}, requestOptions);
3123
3223
  });
3124
3224
  }
3225
+ /**
3226
+ * Paginated session events across turns (newest first); subscribe to a running turn for live events.
3227
+ *
3228
+ * @param opts.pageToken - Token from the previous response nextPageToken.
3229
+ * @param opts.lastTurnId - Newest turn in the listing window (initial load only). Omit to use the session last turn.
3230
+ * @param opts.limit - Page size. Default 100.
3231
+ * @param requestOptions - Overrides client timeout, retries, abortSignal, headers, queryParams.
3232
+ * @returns {Promise<core.Page<TrueFoundryGatewayApi.SessionEventItem, TrueFoundryGatewayApi.ListSessionEventsResponse>>} Paginated session events.
3233
+ */
3234
+ listEvents(opts, requestOptions) {
3235
+ return __classPrivateFieldGet3(this, _AgentSession_client, "f").agents.sessions.listEvents(this.id, opts, requestOptions);
3236
+ }
3125
3237
  };
3126
3238
  _AgentSession_client = /* @__PURE__ */ new WeakMap();
3127
3239
 
3128
- // src/bindDraftAgentSession.ts
3240
+ // src/private/bindDraftAgentSession.ts
3129
3241
  var inflightByDraftId = /* @__PURE__ */ new Map();
3130
3242
  function getGatewayFromSessionClient(client) {
3131
3243
  const internal = client;
@@ -3260,7 +3372,7 @@ function resolveTrueFoundryAgentRuntimeOptions(options) {
3260
3372
  };
3261
3373
  }
3262
3374
 
3263
- // src/useDraftAgentSpec.ts
3375
+ // src/private/useDraftAgentSpec.ts
3264
3376
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3265
3377
  var SPEC_SYNC_DEBOUNCE_MS = 400;
3266
3378
  function useDraftAgentSpec({
@@ -3408,11 +3520,13 @@ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMem
3408
3520
 
3409
3521
  // src/loadSessionSnapshot.ts
3410
3522
  var inflightBySessionId2 = /* @__PURE__ */ new Map();
3411
- function loadSessionSnapshot(client, sessionId, concurrency, sessionOptions) {
3523
+ function loadSessionSnapshot(client, sessionId, sessionOptions, onProgress) {
3412
3524
  const cacheKey = sessionOptions?.draftGateway != null ? `draft:${sessionId}` : sessionId;
3413
3525
  let inflight = inflightBySessionId2.get(cacheKey);
3414
3526
  if (inflight == null) {
3415
- inflight = getSession(client, sessionId, sessionOptions).then((session) => buildSnapshotFromSession(session, concurrency)).finally(() => {
3527
+ inflight = getSession(client, sessionId, sessionOptions).then(
3528
+ (session) => buildSnapshotFromSessionEvents(session, onProgress)
3529
+ ).finally(() => {
3416
3530
  if (inflightBySessionId2.get(cacheKey) === inflight) {
3417
3531
  inflightBySessionId2.delete(cacheKey);
3418
3532
  }
@@ -3654,6 +3768,7 @@ function resolveTurnInput(snapshot, turnId) {
3654
3768
  function useTrueFoundryAgentMessages({
3655
3769
  client,
3656
3770
  sessionId,
3771
+ isMain,
3657
3772
  listEventsConcurrency,
3658
3773
  onError,
3659
3774
  initializeSession,
@@ -3667,13 +3782,21 @@ function useTrueFoundryAgentMessages({
3667
3782
  const [snapshot, setSnapshot] = useState2(createEmptySessionSnapshot);
3668
3783
  const [isRunning, setIsRunning] = useState2(false);
3669
3784
  const [isLoading, setIsLoading] = useState2(false);
3785
+ const [loadRetryTrigger, setLoadRetryTrigger] = useState2(0);
3670
3786
  const snapshotRef = useRef2(snapshot);
3671
3787
  snapshotRef.current = snapshot;
3788
+ const onErrorRef = useRef2(onError);
3789
+ onErrorRef.current = onError;
3790
+ const resolveConversationSessionIdRef = useRef2(resolveConversationSessionId);
3791
+ resolveConversationSessionIdRef.current = resolveConversationSessionId;
3792
+ const initializeSessionRef = useRef2(initializeSession);
3793
+ initializeSessionRef.current = initializeSession;
3672
3794
  const createdAtByMessageIdRef = useRef2(/* @__PURE__ */ new Map());
3673
3795
  const abortControllerRef = useRef2(null);
3674
3796
  const activeRunRef = useRef2(null);
3675
3797
  const runningTurnRef = useRef2(void 0);
3676
3798
  const loadGenerationRef = useRef2(0);
3799
+ const streamGenerationRef = useRef2(0);
3677
3800
  const lazilyCreatedSessionIdRef = useRef2(void 0);
3678
3801
  const projectOptions = useMemo2(
3679
3802
  () => ({
@@ -3693,41 +3816,62 @@ function useTrueFoundryAgentMessages({
3693
3816
  () => projectSessionMessages(snapshot, projectOptions),
3694
3817
  [snapshot, projectOptions]
3695
3818
  );
3696
- const applyStreamUpdate = useCallback2(
3697
- (update, turnId, isContinuation) => {
3698
- setSnapshot(
3699
- (prev) => replaceSessionSnapshot(prev, {
3700
- activeStream: {
3701
- turnId,
3702
- update,
3703
- isContinuation
3704
- }
3705
- })
3706
- );
3707
- },
3708
- []
3709
- );
3710
3819
  const runStream = useCallback2(
3711
3820
  (createStream, turnId, isContinuation) => {
3821
+ const streamGeneration = ++streamGenerationRef.current;
3712
3822
  abortControllerRef.current?.abort();
3713
3823
  const abortController = new AbortController();
3714
3824
  abortControllerRef.current = abortController;
3715
3825
  setIsRunning(true);
3716
3826
  const run = (async () => {
3827
+ let pendingStreamUpdate = null;
3828
+ let streamUpdateRaf = null;
3829
+ const flushPendingStreamUpdate = () => {
3830
+ streamUpdateRaf = null;
3831
+ const pending = pendingStreamUpdate;
3832
+ pendingStreamUpdate = null;
3833
+ if (pending == null || streamGeneration !== streamGenerationRef.current) {
3834
+ return;
3835
+ }
3836
+ const { update, turnId: pendingTurnId, isContinuation: pendingIsContinuation } = pending;
3837
+ setSnapshot(
3838
+ (prev) => replaceSessionSnapshot(prev, {
3839
+ activeStream: {
3840
+ turnId: pendingTurnId,
3841
+ update,
3842
+ isContinuation: pendingIsContinuation
3843
+ }
3844
+ })
3845
+ );
3846
+ };
3847
+ const applyStreamUpdate = (update) => {
3848
+ pendingStreamUpdate = { update, turnId, isContinuation };
3849
+ if (streamUpdateRaf == null) {
3850
+ streamUpdateRaf = requestAnimationFrame(flushPendingStreamUpdate);
3851
+ }
3852
+ };
3717
3853
  try {
3718
3854
  for await (const update of createStream(abortController.signal)) {
3719
3855
  if (abortController.signal.aborted) {
3720
3856
  return;
3721
3857
  }
3722
- applyStreamUpdate(update, turnId, isContinuation);
3858
+ applyStreamUpdate(update);
3723
3859
  }
3724
3860
  } catch (error) {
3725
3861
  if (error instanceof Error && error.name === "AbortError") {
3726
3862
  return;
3727
3863
  }
3728
- onError?.(error);
3864
+ onErrorRef.current?.(error);
3729
3865
  throw error;
3730
3866
  } finally {
3867
+ if (streamUpdateRaf != null) {
3868
+ cancelAnimationFrame(streamUpdateRaf);
3869
+ streamUpdateRaf = null;
3870
+ }
3871
+ if (streamGeneration !== streamGenerationRef.current) {
3872
+ return;
3873
+ }
3874
+ flushPendingStreamUpdate();
3731
3875
  if (abortControllerRef.current === abortController) {
3732
3876
  abortControllerRef.current = null;
3733
3877
  }
@@ -3758,7 +3902,7 @@ function useTrueFoundryAgentMessages({
3758
3902
  });
3759
3903
  return run;
3760
3904
  },
3761
- [applyStreamUpdate, onError]
3905
+ [onError]
3762
3906
  );
3763
3907
  const load = useCallback2(async () => {
3764
3908
  if (sessionId == null) {
@@ -3766,22 +3910,33 @@ function useTrueFoundryAgentMessages({
3766
3910
  setSnapshot(createEmptySessionSnapshot());
3767
3911
  return;
3768
3912
  }
3913
+ if (isMain === false) return;
3914
+ if (lazilyCreatedSessionIdRef.current != null && sessionId !== lazilyCreatedSessionIdRef.current) {
3915
+ lazilyCreatedSessionIdRef.current = void 0;
3916
+ }
3769
3917
  if (sessionId === lazilyCreatedSessionIdRef.current) {
3770
3918
  return;
3771
3919
  }
3772
3920
  const generation = ++loadGenerationRef.current;
3921
+ ++streamGenerationRef.current;
3773
3922
  abortControllerRef.current?.abort();
3923
+ createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
3924
+ setSnapshot(createEmptySessionSnapshot());
3774
3925
  setIsLoading(true);
3775
3926
  try {
3776
3927
  const conversationSessionId = await resolveActiveSessionId(
3777
3928
  sessionId,
3778
- resolveConversationSessionId
3929
+ resolveConversationSessionIdRef.current
3779
3930
  );
3780
3931
  const loadedSnapshot = await loadSessionSnapshot(
3781
3932
  client,
3782
3933
  conversationSessionId,
3783
- listEventsConcurrency,
3784
- sessionOptions
3934
+ sessionOptions,
3935
+ (snap) => {
3936
+ if (generation === loadGenerationRef.current) {
3937
+ setSnapshot(snap);
3938
+ }
3939
+ }
3785
3940
  );
3786
3941
  if (generation !== loadGenerationRef.current) {
3787
3942
  return;
@@ -3789,30 +3944,33 @@ function useTrueFoundryAgentMessages({
3789
3944
  createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
3790
3945
  setSnapshot(loadedSnapshot);
3791
3946
  runningTurnRef.current = loadedSnapshot.runningTurn;
3947
+ setIsLoading(false);
3792
3948
  if (loadedSnapshot.runningTurn != null) {
3793
3949
  const turn = loadedSnapshot.runningTurn;
3794
3950
  const isContinuation = !extractTurnUserText(turn.input);
3795
- await runStream(
3951
+ void runStream(
3796
3952
  (signal) => resumeTurnStream(
3797
3953
  turn,
3798
- snapshotRef.current.fold,
3954
+ loadedSnapshot.fold,
3799
3955
  signal,
3800
3956
  void 0,
3801
- snapshotRef.current.groupRootBaseline
3957
+ loadedSnapshot.groupRootBaseline
3802
3958
  ),
3803
3959
  turn.id,
3804
3960
  isContinuation
3805
- );
3961
+ ).catch(() => void 0);
3806
3962
  }
3807
3963
  } catch (error) {
3808
- onError?.(error);
3964
+ if (generation === loadGenerationRef.current) {
3965
+ onErrorRef.current?.(error);
3966
+ }
3809
3967
  throw error;
3810
3968
  } finally {
3811
3969
  if (generation === loadGenerationRef.current) {
3812
3970
  setIsLoading(false);
3813
3971
  }
3814
3972
  }
3815
- }, [client, listEventsConcurrency, onError, resolveConversationSessionId, runStream, sessionId, sessionOptions]);
3973
+ }, [client, runStream, sessionId, sessionOptions, loadRetryTrigger, isMain]);
3816
3974
  useEffect2(() => {
3817
3975
  void load().catch(() => void 0);
3818
3976
  }, [load]);
@@ -3820,16 +3978,16 @@ function useTrueFoundryAgentMessages({
3820
3978
  async (options) => {
3821
3979
  let activeSessionId = sessionId;
3822
3980
  if (activeSessionId == null) {
3823
- if (initializeSession == null) {
3981
+ if (initializeSessionRef.current == null) {
3824
3982
  throw new Error("Cannot send a turn without an active session.");
3825
3983
  }
3826
- const { remoteId } = await initializeSession();
3984
+ const { remoteId } = await initializeSessionRef.current();
3827
3985
  activeSessionId = remoteId;
3828
3986
  lazilyCreatedSessionIdRef.current = remoteId;
3829
3987
  }
3830
3988
  const conversationSessionId = await resolveActiveSessionId(
3831
3989
  activeSessionId,
3832
- resolveConversationSessionId
3990
+ resolveConversationSessionIdRef.current
3833
3991
  );
3834
3992
  const session = await getSession(client, conversationSessionId, sessionOptions);
3835
3993
  const isContinuation = "inputs" in options || "resumeMcpAuth" in options && options.resumeMcpAuth === true;
@@ -3841,29 +3999,48 @@ function useTrueFoundryAgentMessages({
3841
3999
  options.inputs
3842
4000
  );
3843
4001
  }
3844
- setSnapshot(
3845
- (prev) => commitActiveStream(
3846
- prev,
3847
- "inputs" in options ? options.inputs : void 0
3848
- )
3849
- );
4002
+ const branchBase = "userMessage" in options ? options.branchFromSnapshot : void 0;
3850
4003
  let groupRootBaseline;
3851
- if ("userMessage" in options) {
3852
- const rootBucket = snapshotRef.current.fold.threads.get(ROOT_THREAD_ID);
4004
+ if (branchBase != null && "userMessage" in options) {
4005
+ const rootBucket = branchBase.fold.threads.get(ROOT_THREAD_ID);
3853
4006
  groupRootBaseline = [...rootBucket?.modelMessageIds ?? []];
4007
+ const nextSnapshot = replaceSessionSnapshot(branchBase, {
4008
+ pendingUser: {
4009
+ turnId,
4010
+ content: options.userMessage,
4011
+ createdAt: /* @__PURE__ */ new Date()
4012
+ },
4013
+ activeStream: void 0,
4014
+ groupRootBaseline
4015
+ });
4016
+ snapshotRef.current = nextSnapshot;
4017
+ setSnapshot(nextSnapshot);
4018
+ } else {
3854
4019
  setSnapshot(
3855
- (prev) => replaceSessionSnapshot(prev, {
3856
- pendingUser: {
3857
- turnId,
3858
- content: options.userMessage,
3859
- createdAt: /* @__PURE__ */ new Date()
3860
- },
3861
- activeStream: void 0,
3862
- groupRootBaseline
3863
- })
4020
+ (prev) => commitActiveStream(
4021
+ prev,
4022
+ "inputs" in options ? options.inputs : void 0
4023
+ )
3864
4024
  );
3865
- } else {
3866
- groupRootBaseline = snapshotRef.current.groupRootBaseline ?? computeGroupRootBaseline(snapshotRef.current.turns);
4025
+ if ("userMessage" in options) {
4026
+ const rootBucket = snapshotRef.current.fold.threads.get(ROOT_THREAD_ID);
4027
+ groupRootBaseline = [...rootBucket?.modelMessageIds ?? []];
4028
+ setSnapshot((prev) => {
4029
+ const next = replaceSessionSnapshot(prev, {
4030
+ pendingUser: {
4031
+ turnId,
4032
+ content: options.userMessage,
4033
+ createdAt: /* @__PURE__ */ new Date()
4034
+ },
4035
+ activeStream: void 0,
4036
+ groupRootBaseline
4037
+ });
4038
+ snapshotRef.current = next;
4039
+ return next;
4040
+ });
4041
+ } else {
4042
+ groupRootBaseline = snapshotRef.current.groupRootBaseline ?? computeGroupRootBaseline(snapshotRef.current.turns);
4043
+ }
3867
4044
  }
3868
4045
  await runStream(
3869
4046
  (signal) => {
@@ -3900,7 +4077,7 @@ function useTrueFoundryAgentMessages({
3900
4077
  isContinuation
3901
4078
  );
3902
4079
  },
3903
- [client, initializeSession, resolveConversationSessionId, runStream, sessionId, sessionOptions]
4080
+ [client, runStream, sessionId, sessionOptions]
3904
4081
  );
3905
4082
  const cancel = useCallback2(async () => {
3906
4083
  if (sessionId == null) {
@@ -3909,12 +4086,12 @@ function useTrueFoundryAgentMessages({
3909
4086
  }
3910
4087
  const conversationSessionId = await resolveActiveSessionId(
3911
4088
  sessionId,
3912
- resolveConversationSessionId
4089
+ resolveConversationSessionIdRef.current
3913
4090
  );
3914
4091
  const session = await getSession(client, conversationSessionId, sessionOptions);
3915
4092
  await session.cancel().catch(() => void 0);
3916
4093
  await activeRunRef.current?.catch(() => void 0);
3917
- }, [client, resolveConversationSessionId, sessionId, sessionOptions]);
4094
+ }, [client, sessionId, sessionOptions]);
3918
4095
  const isRunningRef = useRef2(isRunning);
3919
4096
  isRunningRef.current = isRunning;
3920
4097
  const trySendCollectedRequiredActions = useCallback2(
@@ -3929,10 +4106,10 @@ function useTrueFoundryAgentMessages({
3929
4106
  }
3930
4107
  const inputs = collectRequiredActionInputs(paused);
3931
4108
  if (inputs.length > 0) {
3932
- void sendTurn({ inputs }).catch((error) => onError?.(error));
4109
+ void sendTurn({ inputs }).catch((error) => onErrorRef.current?.(error));
3933
4110
  }
3934
4111
  },
3935
- [onError, projectOptions, sendTurn]
4112
+ [projectOptions, sendTurn]
3936
4113
  );
3937
4114
  const respondToToolApproval = useCallback2(
3938
4115
  (response) => {
@@ -3998,7 +4175,7 @@ function useTrueFoundryAgentMessages({
3998
4175
  await cancel();
3999
4176
  const conversationSessionId = await resolveActiveSessionId(
4000
4177
  activeSessionId,
4001
- resolveConversationSessionId
4178
+ resolveConversationSessionIdRef.current
4002
4179
  );
4003
4180
  const session = await getSession(client, conversationSessionId, sessionOptions);
4004
4181
  const previousTurnId = await resolveGatewayBranchPreviousTurnId(
@@ -4011,17 +4188,18 @@ function useTrueFoundryAgentMessages({
4011
4188
  listEventsConcurrency
4012
4189
  );
4013
4190
  createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
4191
+ snapshotRef.current = rewound;
4014
4192
  setSnapshot(rewound);
4015
4193
  await sendTurn({
4016
4194
  userMessage,
4017
- previousTurnId
4195
+ previousTurnId,
4196
+ branchFromSnapshot: rewound
4018
4197
  });
4019
4198
  },
4020
4199
  [
4021
4200
  cancel,
4022
4201
  client,
4023
4202
  listEventsConcurrency,
4024
- resolveConversationSessionId,
4025
4203
  sendTurn,
4026
4204
  sessionId,
4027
4205
  sessionOptions
@@ -4054,11 +4232,14 @@ function useTrueFoundryAgentMessages({
4054
4232
  },
4055
4233
  [branchFromTurn]
4056
4234
  );
4235
+ const retryLoad = useCallback2(() => {
4236
+ setLoadRetryTrigger((n) => n + 1);
4237
+ }, []);
4057
4238
  return {
4058
4239
  messages,
4059
4240
  isRunning,
4060
4241
  isLoading,
4061
- load,
4242
+ retryLoad,
4062
4243
  sendTurn,
4063
4244
  cancel,
4064
4245
  respondToToolApproval,
@@ -4088,6 +4269,9 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4088
4269
  (state) => agent.mode === "draft" ? state.threadListItem.remoteId ?? void 0 : void 0
4089
4270
  );
4090
4271
  const sessionId = useAuiState((state) => state.threadListItem.remoteId ?? void 0);
4272
+ const isMain = useAuiState(
4273
+ (state) => state.threads.mainThreadId === state.threadListItem.id
4274
+ );
4091
4275
  const draftSpec = useDraftAgentSpec({
4092
4276
  draftSessionId,
4093
4277
  draftBridge: draftBridgeRef.current,
@@ -4112,10 +4296,12 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4112
4296
  respondToToolResponse,
4113
4297
  resumeRun,
4114
4298
  editFromTurn,
4115
- resetFromTurn
4299
+ resetFromTurn,
4300
+ retryLoad
4116
4301
  } = useTrueFoundryAgentMessages({
4117
4302
  client,
4118
4303
  sessionId,
4304
+ isMain,
4119
4305
  listEventsConcurrency,
4120
4306
  onError,
4121
4307
  initializeSession,
@@ -4187,6 +4373,7 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4187
4373
  resetFromTurn: (turnId) => resetFromTurn(turnId).catch((error) => {
4188
4374
  onError?.(error);
4189
4375
  }),
4376
+ reload: retryLoad,
4190
4377
  draft: draftExtras
4191
4378
  }),
4192
4379
  unstable_enableToolInvocations: true,
@@ -4235,32 +4422,32 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4235
4422
  });
4236
4423
  }
4237
4424
  function useTrueFoundryAgentRuntime(options) {
4238
- const resolved = useMemo3(
4239
- () => resolveTrueFoundryAgentRuntimeOptions(options),
4240
- [options]
4241
- );
4425
+ const resolved = resolveTrueFoundryAgentRuntimeOptions(options);
4242
4426
  const { client, agent, gateway } = resolved;
4243
4427
  const pendingAgentSpecRef = useRef3(
4244
4428
  agent.mode === "draft" ? agent.defaultAgentSpec : void 0
4245
4429
  );
4430
+ const agentMode = agent.mode;
4431
+ const namedAgentName = agent.mode === "named" ? agent.agentName : void 0;
4246
4432
  const threadListAdapter = useMemo3(() => {
4247
- if (agent.mode === "draft") {
4433
+ if (agentMode === "draft") {
4248
4434
  if (gateway == null) {
4249
4435
  throw new Error(
4250
4436
  "Draft agent mode requires a `gateway` TrueFoundryGateway client."
4251
4437
  );
4252
4438
  }
4439
+ const draftAgent = agent;
4253
4440
  return createTrueFoundryDraftThreadListAdapter({
4254
4441
  gateway,
4255
- defaultAgentSpec: agent.defaultAgentSpec,
4256
- getAgentSpec: () => pendingAgentSpecRef.current ?? agent.defaultAgentSpec
4442
+ defaultAgentSpec: draftAgent.defaultAgentSpec,
4443
+ getAgentSpec: () => pendingAgentSpecRef.current ?? draftAgent.defaultAgentSpec
4257
4444
  });
4258
4445
  }
4259
4446
  return createTrueFoundryThreadListAdapter({
4260
4447
  client,
4261
- agentName: agent.agentName
4448
+ agentName: namedAgentName
4262
4449
  });
4263
- }, [agent, client, gateway]);
4450
+ }, [agentMode, namedAgentName, client, gateway]);
4264
4451
  return useRemoteThreadListRuntime({
4265
4452
  allowNesting: true,
4266
4453
  adapter: threadListAdapter,
@@ -4331,6 +4518,10 @@ var useTrueFoundryCancel = () => {
4331
4518
  const aui = useAui2();
4332
4519
  return () => trueFoundryExtras.get(aui).cancel();
4333
4520
  };
4521
+ var useTrueFoundryReload = () => {
4522
+ const aui = useAui2();
4523
+ return () => trueFoundryExtras.get(aui).reload();
4524
+ };
4334
4525
  var useTrueFoundryResetFromTurn = () => {
4335
4526
  const aui = useAui2();
4336
4527
  return (turnId) => trueFoundryExtras.get(aui).resetFromTurn(turnId);
@@ -4429,6 +4620,7 @@ export {
4429
4620
  useTrueFoundryCancel,
4430
4621
  useTrueFoundryDownloadSandboxFile,
4431
4622
  useTrueFoundryMcpAuth,
4623
+ useTrueFoundryReload,
4432
4624
  useTrueFoundryResetFromTurn,
4433
4625
  useTrueFoundryRespondToToolApproval,
4434
4626
  useTrueFoundryRespondToToolResponse,