@truefoundry/assistant-ui-runtime 0.1.0-rc.1 → 0.1.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.
Files changed (33) hide show
  1. package/README.md +45 -77
  2. package/dist/index.d.ts +14 -2
  3. package/dist/index.js +697 -147
  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 +502 -1
  8. package/src/convertTurnMessages.ts +471 -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 +24 -0
  14. package/src/index.ts +7 -5
  15. package/src/loadSessionSnapshot.test.ts +21 -6
  16. package/src/loadSessionSnapshot.ts +9 -5
  17. package/src/{bindDraftAgentSession.test.ts → private/bindDraftAgentSession.test.ts} +3 -2
  18. package/src/{draftSessionBridge.ts → private/draftSessionBridge.ts} +8 -2
  19. package/src/{truefoundryDraftThreadListAdapter.ts → private/truefoundryDraftThreadListAdapter.ts} +1 -1
  20. package/src/private/useDraftAgentSpec.test.tsx +153 -0
  21. package/src/{useDraftAgentSpec.ts → private/useDraftAgentSpec.ts} +80 -3
  22. package/src/sessionSnapshot.ts +48 -2
  23. package/src/sessions.ts +1 -1
  24. package/src/streamTurn.test.ts +34 -0
  25. package/src/streamTurn.ts +9 -1
  26. package/src/truefoundryExtras.ts +5 -1
  27. package/src/types.ts +1 -1
  28. package/src/useTrueFoundryAgentMessages.test.tsx +275 -1
  29. package/src/useTrueFoundryAgentMessages.ts +263 -70
  30. package/src/useTrueFoundryAgentRuntime.ts +51 -13
  31. /package/src/{agentSpec.ts → private/agentSpec.ts} +0 -0
  32. /package/src/{bindDraftAgentSession.ts → private/bindDraftAgentSession.ts} +0 -0
  33. /package/src/{truefoundryDraftThreadListAdapter.test.ts → private/truefoundryDraftThreadListAdapter.test.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,281 @@ 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;
1307
+ var MAX_HISTORY_BOUNDARY_PAGES = 10;
1308
+ function trimIncompleteLeadingEvents(itemsAsc) {
1309
+ const start = itemsAsc.findIndex((item) => item.event.type === "turn.created");
1310
+ if (start === -1) {
1311
+ return [];
1312
+ }
1313
+ return itemsAsc.slice(start);
1314
+ }
1315
+ function oldestCompleteTurnGroupState(itemsAsc) {
1316
+ const trimmed = trimIncompleteLeadingEvents(itemsAsc);
1317
+ if (trimmed.length === 0) {
1318
+ return "incomplete";
1319
+ }
1320
+ const created = trimmed[0];
1321
+ if (created?.event.type !== "turn.created") {
1322
+ return "incomplete";
1323
+ }
1324
+ const hasDone = trimmed.some(
1325
+ (item) => item.turnId === created.turnId && item.event.type === "turn.done"
1326
+ );
1327
+ if (!hasDone) {
1328
+ return "incomplete";
1329
+ }
1330
+ return extractTurnUserText(created.event.input) != null ? "user-group" : "continuation";
1331
+ }
1332
+ async function fetchSessionEventsPage(session, options) {
1333
+ const page = await session.listEvents({
1334
+ limit: SESSION_EVENTS_PAGE_SIZE,
1335
+ ...options?.lastTurnId != null ? { lastTurnId: options.lastTurnId } : {},
1336
+ ...options?.pageToken != null ? { pageToken: options.pageToken } : {}
1337
+ });
1338
+ const response = page.response;
1339
+ const olderPageToken = response.pagination?.nextPageToken;
1340
+ const hasOlder = typeof page.hasNextPage === "function" ? page.hasNextPage() : olderPageToken != null && olderPageToken !== "";
1341
+ return {
1342
+ itemsNewestFirst: page.data,
1343
+ ...olderPageToken != null && olderPageToken !== "" ? { olderPageToken } : {},
1344
+ hasOlder
1345
+ };
1346
+ }
1347
+ async function fetchSessionEventsWindow(session, options) {
1348
+ let itemsNewestFirst = [];
1349
+ let pageToken = options?.pageToken;
1350
+ let olderPageToken;
1351
+ let hasOlder = false;
1352
+ for (let pageCount = 0; pageCount < MAX_HISTORY_BOUNDARY_PAGES; pageCount++) {
1353
+ const page = await fetchSessionEventsPage(session, {
1354
+ ...options,
1355
+ ...pageToken != null ? { pageToken } : {}
1356
+ });
1357
+ itemsNewestFirst = [...itemsNewestFirst, ...page.itemsNewestFirst];
1358
+ olderPageToken = page.olderPageToken;
1359
+ hasOlder = page.hasOlder;
1360
+ const itemsAsc2 = trimIncompleteLeadingEvents([...itemsNewestFirst].reverse());
1361
+ const groupState = oldestCompleteTurnGroupState(itemsAsc2);
1362
+ if (groupState === "user-group" || !hasOlder) {
1363
+ return {
1364
+ itemsAsc: itemsAsc2,
1365
+ ...olderPageToken != null ? { olderPageToken } : {},
1366
+ hasOlder
1367
+ };
1368
+ }
1369
+ if (olderPageToken == null) {
1370
+ return { itemsAsc: itemsAsc2, hasOlder: false };
1371
+ }
1372
+ pageToken = olderPageToken;
1373
+ }
1374
+ const itemsAsc = trimIncompleteLeadingEvents([...itemsNewestFirst].reverse());
1375
+ return {
1376
+ itemsAsc,
1377
+ ...olderPageToken != null ? { olderPageToken } : {},
1378
+ hasOlder
1379
+ };
1380
+ }
1381
+ async function fetchAllSessionEvents(session, options) {
1382
+ const items = [];
1383
+ for await (const item of await session.listEvents({
1384
+ limit: SESSION_EVENTS_PAGE_SIZE,
1385
+ ...options?.lastTurnId != null ? { lastTurnId: options.lastTurnId } : {}
1386
+ })) {
1387
+ items.push(item);
1388
+ }
1389
+ items.reverse();
1390
+ return items;
1391
+ }
1392
+ function cloneThreadBucket(bucket) {
1393
+ return {
1394
+ events: new Map(bucket.events),
1395
+ modelMessageIds: [...bucket.modelMessageIds],
1396
+ toolResults: new Map(bucket.toolResults),
1397
+ pendingApprovals: new Map(bucket.pendingApprovals),
1398
+ approvalDecisions: new Map(bucket.approvalDecisions),
1399
+ pendingResponses: new Map(bucket.pendingResponses),
1400
+ done: bucket.done,
1401
+ ...bucket.title != null ? { title: bucket.title } : {},
1402
+ ...bucket.agentInfo != null ? { agentInfo: bucket.agentInfo } : {}
1403
+ };
1404
+ }
1405
+ function prependFoldState(older, newer) {
1406
+ const result = new PeerThreadFoldState();
1407
+ const threadIds = /* @__PURE__ */ new Set([...older.threads.keys(), ...newer.threads.keys()]);
1408
+ for (const threadId of threadIds) {
1409
+ const olderBucket = older.threads.get(threadId);
1410
+ const newerBucket = newer.threads.get(threadId);
1411
+ if (olderBucket == null && newerBucket != null) {
1412
+ result.threads.set(threadId, cloneThreadBucket(newerBucket));
1413
+ continue;
1414
+ }
1415
+ if (newerBucket == null && olderBucket != null) {
1416
+ result.threads.set(threadId, cloneThreadBucket(olderBucket));
1417
+ continue;
1418
+ }
1419
+ if (olderBucket == null || newerBucket == null) {
1420
+ continue;
1421
+ }
1422
+ result.threads.set(threadId, {
1423
+ events: new Map([...olderBucket.events, ...newerBucket.events]),
1424
+ modelMessageIds: [
1425
+ ...olderBucket.modelMessageIds,
1426
+ ...newerBucket.modelMessageIds
1427
+ ],
1428
+ toolResults: new Map([
1429
+ ...olderBucket.toolResults,
1430
+ ...newerBucket.toolResults
1431
+ ]),
1432
+ pendingApprovals: new Map([
1433
+ ...olderBucket.pendingApprovals,
1434
+ ...newerBucket.pendingApprovals
1435
+ ]),
1436
+ approvalDecisions: new Map([
1437
+ ...olderBucket.approvalDecisions,
1438
+ ...newerBucket.approvalDecisions
1439
+ ]),
1440
+ pendingResponses: new Map([
1441
+ ...olderBucket.pendingResponses,
1442
+ ...newerBucket.pendingResponses
1443
+ ]),
1444
+ done: newerBucket.done || olderBucket.done,
1445
+ ...newerBucket.title != null || olderBucket.title != null ? { title: newerBucket.title ?? olderBucket.title } : {},
1446
+ ...newerBucket.agentInfo != null || olderBucket.agentInfo != null ? { agentInfo: newerBucket.agentInfo ?? olderBucket.agentInfo } : {}
1447
+ });
1448
+ }
1449
+ for (const [threadId, link] of older.threadParents) {
1450
+ result.threadParents.set(threadId, link);
1451
+ }
1452
+ for (const [threadId, link] of newer.threadParents) {
1453
+ result.threadParents.set(threadId, link);
1454
+ }
1455
+ return result;
1456
+ }
1457
+ function ingestSessionEventsIntoSnapshot(snapshot, items, onTurnComplete) {
1458
+ let currentTurnId = null;
1459
+ let currentCreatedEvent = null;
1460
+ let currentContentEvents = [];
1461
+ let beforeCount = 0;
1462
+ for (const item of items) {
1463
+ const { turnId, event } = item;
1464
+ if (event.type === "turn.created") {
1465
+ currentTurnId = turnId;
1466
+ currentCreatedEvent = event;
1467
+ currentContentEvents = [];
1468
+ beforeCount = snapshot.fold.threads.get(ROOT_THREAD_ID)?.modelMessageIds.length ?? 0;
1469
+ } else if (event.type === "turn.done") {
1470
+ if (currentTurnId == null || currentCreatedEvent == null) {
1471
+ continue;
1472
+ }
1473
+ const afterBucket = snapshot.fold.threads.get(ROOT_THREAD_ID);
1474
+ const rootModelMessageIds = (afterBucket?.modelMessageIds ?? []).slice(
1475
+ beforeCount
1476
+ );
1477
+ const sandboxEvent = currentContentEvents.find(
1478
+ (ev) => ev.type === "sandbox.created"
1479
+ );
1480
+ applyUserToolResponsesToFold(
1481
+ snapshot.fold,
1482
+ currentCreatedEvent.input ?? []
1483
+ );
1484
+ snapshot.turns.push(
1485
+ sessionEventsToSessionRecord(
1486
+ currentTurnId,
1487
+ currentCreatedEvent,
1488
+ event,
1489
+ rootModelMessageIds,
1490
+ sandboxEvent?.sandboxId
1491
+ )
1492
+ );
1493
+ onTurnComplete?.(replaceSessionSnapshot(snapshot, {}));
1494
+ currentTurnId = null;
1495
+ currentCreatedEvent = null;
1496
+ currentContentEvents = [];
1497
+ } else {
1498
+ if (currentTurnId != null) {
1499
+ ingestTurnEvent(snapshot.fold, event);
1500
+ currentContentEvents.push(event);
1501
+ }
1502
+ }
1503
+ }
1504
+ }
1505
+ function attachRunningTurn(snapshot, runningTurn) {
1506
+ if (runningTurn == null) {
1507
+ return snapshot;
1508
+ }
1509
+ const pendingUserText = extractTurnUserText(runningTurn.input);
1510
+ return replaceSessionSnapshot(snapshot, {
1511
+ runningTurn,
1512
+ unstable_resume: true,
1513
+ groupRootBaseline: computeGroupRootBaseline(snapshot.turns),
1514
+ ...pendingUserText ? {
1515
+ pendingUser: {
1516
+ turnId: runningTurn.id,
1517
+ content: extractTurnUserMessageContent(runningTurn.input),
1518
+ createdAt: new Date(runningTurn.createdAt)
1519
+ }
1520
+ } : {}
1521
+ });
1522
+ }
1523
+ async function buildSnapshotFromSessionEvents(session, onProgress) {
1524
+ const turnsPage = await session.listTurns({ limit: 1 });
1525
+ const newestTurn = turnsPage.data[0];
1526
+ const runningTurn = newestTurn?.state?.status === "running" ? newestTurn : void 0;
1527
+ const window = await fetchSessionEventsWindow(session);
1528
+ const historyPagination = {
1529
+ hasOlder: window.hasOlder,
1530
+ ...window.olderPageToken != null ? { olderPageToken: window.olderPageToken } : {}
1531
+ };
1532
+ const snapshot = createEmptySessionSnapshot();
1533
+ ingestSessionEventsIntoSnapshot(snapshot, window.itemsAsc, onProgress);
1534
+ const withHistory = replaceSessionSnapshot(snapshot, {
1535
+ historyEvents: window.itemsAsc,
1536
+ historyPagination
1537
+ });
1538
+ return attachRunningTurn(withHistory, runningTurn);
1539
+ }
1540
+ async function prependOlderSessionHistory(session, snapshot) {
1541
+ const pagination = snapshot.historyPagination;
1542
+ if (pagination?.hasOlder !== true || pagination.olderPageToken == null) {
1543
+ return snapshot;
1544
+ }
1545
+ const window = await fetchSessionEventsWindow(session, {
1546
+ pageToken: pagination.olderPageToken
1547
+ });
1548
+ if (window.itemsAsc.length === 0) {
1549
+ return replaceSessionSnapshot(snapshot, {
1550
+ historyPagination: { hasOlder: false }
1551
+ });
1552
+ }
1553
+ const olderSnap = createEmptySessionSnapshot();
1554
+ ingestSessionEventsIntoSnapshot(olderSnap, window.itemsAsc);
1555
+ const existingIds = new Set(snapshot.turns.map((turn) => turn.id));
1556
+ const olderTurns = olderSnap.turns.filter((turn) => !existingIds.has(turn.id));
1557
+ const mergedFold = prependFoldState(olderSnap.fold, snapshot.fold);
1558
+ const historyEvents = [
1559
+ ...window.itemsAsc,
1560
+ ...snapshot.historyEvents ?? []
1561
+ ];
1562
+ const olderRootIds = olderTurns.flatMap(
1563
+ (turn) => turn.rootModelMessageIds ?? []
1564
+ );
1565
+ return replaceSessionSnapshot(snapshot, {
1566
+ fold: mergedFold,
1567
+ turns: [...olderTurns, ...snapshot.turns],
1568
+ historyEvents,
1569
+ historyPagination: {
1570
+ hasOlder: window.hasOlder,
1571
+ ...window.olderPageToken != null ? { olderPageToken: window.olderPageToken } : {}
1572
+ },
1573
+ ...snapshot.groupRootBaseline != null ? {
1574
+ groupRootBaseline: [
1575
+ ...olderRootIds,
1576
+ ...snapshot.groupRootBaseline
1577
+ ]
1578
+ } : {}
1579
+ });
1580
+ }
1288
1581
  function assistantStatusFromTurnState(state) {
1289
1582
  switch (state.status) {
1290
1583
  case "done":
@@ -1719,37 +2012,41 @@ function ingestTurnsIntoSnapshot(snapshot, turns, eventArrays) {
1719
2012
  return runningTurn;
1720
2013
  }
1721
2014
  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);
2015
+ const snapshot = await buildSnapshotFromSessionEvents(session);
2016
+ if (snapshot.runningTurn == null) {
2017
+ return snapshot;
1725
2018
  }
1726
- turns.reverse();
1727
- const eventArrays = await fetchAllTurnEventsWithConcurrency(turns, concurrency);
1728
- const snapshot = createEmptySessionSnapshot();
1729
- const runningTurn = ingestTurnsIntoSnapshot(snapshot, turns, eventArrays);
2019
+ const turn = snapshot.runningTurn;
2020
+ const eventArrays = await fetchAllTurnEventsWithConcurrency([turn], concurrency);
2021
+ ingestTurnsIntoSnapshot(snapshot, [turn], eventArrays);
1730
2022
  return replaceSessionSnapshot(snapshot, {
1731
- ...runningTurn != null ? {
1732
- runningTurn,
1733
- unstable_resume: true,
1734
- groupRootBaseline: computeGroupRootBaseline(snapshot.turns)
1735
- } : {}
2023
+ runningTurn: turn,
2024
+ unstable_resume: true,
2025
+ groupRootBaseline: computeGroupRootBaseline(snapshot.turns),
2026
+ pendingUser: void 0
1736
2027
  });
1737
2028
  }
1738
- async function buildSnapshotBeforeTurnIndex(session, turnIndex, concurrency = DEFAULT_LIST_EVENTS_CONCURRENCY, orderedTurns) {
2029
+ async function buildSnapshotBeforeTurn(session, beforeTurnId, concurrency = DEFAULT_LIST_EVENTS_CONCURRENCY) {
2030
+ const turns = await listSessionTurnsOrdered(session);
2031
+ const beforeIndex = turns.findIndex((turn) => turn.id === beforeTurnId);
2032
+ if (beforeIndex === -1) {
2033
+ throw new Error(`Turn ${beforeTurnId} not found in session`);
2034
+ }
2035
+ return buildSnapshotBeforeTurnIndex(session, beforeIndex, concurrency, turns);
2036
+ }
2037
+ async function buildSnapshotBeforeTurnIndex(session, turnIndex, _concurrency = DEFAULT_LIST_EVENTS_CONCURRENCY, orderedTurns) {
1739
2038
  if (turnIndex <= 0) {
1740
2039
  return createEmptySessionSnapshot();
1741
2040
  }
1742
2041
  const turns = orderedTurns ?? await listSessionTurnsOrdered(session);
1743
2042
  const turnsToInclude = turns.slice(0, turnIndex);
1744
- if (turnsToInclude.length === 0) {
2043
+ const lastTurnId = turnsToInclude.at(-1)?.id;
2044
+ if (lastTurnId == null) {
1745
2045
  return createEmptySessionSnapshot();
1746
2046
  }
1747
- const eventArrays = await fetchAllTurnEventsWithConcurrency(
1748
- turnsToInclude,
1749
- concurrency
1750
- );
2047
+ const items = await fetchAllSessionEvents(session, { lastTurnId });
1751
2048
  const snapshot = createEmptySessionSnapshot();
1752
- ingestTurnsIntoSnapshot(snapshot, turnsToInclude, eventArrays);
2049
+ ingestSessionEventsIntoSnapshot(snapshot, items);
1753
2050
  return snapshot;
1754
2051
  }
1755
2052
  async function resolveGatewayBranchPreviousTurnId(session, turnIndex, orderedTurns) {
@@ -1759,6 +2056,11 @@ async function resolveGatewayBranchPreviousTurnId(session, turnIndex, orderedTur
1759
2056
  const turns = orderedTurns ?? await listSessionTurnsOrdered(session);
1760
2057
  return turns[turnIndex - 1]?.id ?? null;
1761
2058
  }
2059
+ async function resolveGatewayBranchPreviousTurnIdForTurn(session, turnId) {
2060
+ const turns = await listSessionTurnsOrdered(session);
2061
+ const turnIndex = turns.findIndex((turn) => turn.id === turnId);
2062
+ return resolveGatewayBranchPreviousTurnId(session, turnIndex, turns);
2063
+ }
1762
2064
  async function listSessionTurnsOrdered(session) {
1763
2065
  const turns = [];
1764
2066
  for await (const turn of await session.listTurns()) {
@@ -2024,7 +2326,8 @@ function repositoryItemsFromMessages(messages) {
2024
2326
  return items;
2025
2327
  }
2026
2328
 
2027
- // src/draftSessionBridge.ts
2329
+ // src/private/draftSessionBridge.ts
2330
+ var DRAFT_SESSION_LAST_UPDATED_AT_HEADER = "x-tfy-session-last-updated-at";
2028
2331
  function createDraftSessionBridge(gateway) {
2029
2332
  async function getDraft(draftSessionId) {
2030
2333
  const response = await gateway.agents.private.draftSessions.get(draftSessionId);
@@ -2036,12 +2339,16 @@ function createDraftSessionBridge(gateway) {
2036
2339
  return draft.agentSpec;
2037
2340
  },
2038
2341
  async syncAgentSpec(draftSessionId, agentSpec) {
2039
- await gateway.agents.private.draftSessions.update(draftSessionId, { agentSpec });
2342
+ const response = await gateway.agents.private.draftSessions.update(
2343
+ draftSessionId,
2344
+ { agentSpec }
2345
+ );
2346
+ return response.data.updatedAt;
2040
2347
  }
2041
2348
  };
2042
2349
  }
2043
2350
 
2044
- // src/agentSpec.ts
2351
+ // src/private/agentSpec.ts
2045
2352
  function mergeAgentSpec(base, update) {
2046
2353
  const { model: modelUpdate, ...rest } = update;
2047
2354
  const next = {
@@ -2069,7 +2376,7 @@ function sessionListStartTimestamp() {
2069
2376
  return start.toISOString();
2070
2377
  }
2071
2378
 
2072
- // src/truefoundryDraftThreadListAdapter.ts
2379
+ // src/private/truefoundryDraftThreadListAdapter.ts
2073
2380
  var THREAD_LIST_PAGE_SIZE = 20;
2074
2381
  function createTrueFoundryDraftThreadListAdapter(options) {
2075
2382
  const { gateway, defaultAgentSpec, getAgentSpec } = options;
@@ -2137,7 +2444,7 @@ var EMPTY_DRAFT_EXTRAS = {
2137
2444
  }
2138
2445
  };
2139
2446
 
2140
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/core/fetcher/HttpResponsePromise.mjs
2447
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/core/fetcher/HttpResponsePromise.mjs
2141
2448
  var __awaiter = function(thisArg, _arguments, P, generator) {
2142
2449
  function adopt(value) {
2143
2450
  return value instanceof P ? value : new P(function(resolve) {
@@ -2252,7 +2559,7 @@ var HttpResponsePromise = class _HttpResponsePromise extends Promise {
2252
2559
  }
2253
2560
  };
2254
2561
 
2255
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/core/pagination/Page.mjs
2562
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/core/pagination/Page.mjs
2256
2563
  var __awaiter2 = function(thisArg, _arguments, P, generator) {
2257
2564
  function adopt(value) {
2258
2565
  return value instanceof P ? value : new P(function(resolve) {
@@ -2407,7 +2714,7 @@ var Page = class {
2407
2714
  }
2408
2715
  };
2409
2716
 
2410
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/agents/TurnStreamData.mjs
2717
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/agents/TurnStreamData.mjs
2411
2718
  function parseSequenceNumber(id) {
2412
2719
  if (!id) {
2413
2720
  throw new Error("Missing SSE sequence number id.");
@@ -2419,7 +2726,7 @@ function parseSequenceNumber(id) {
2419
2726
  return parsed;
2420
2727
  }
2421
2728
 
2422
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/agents/Turn.mjs
2729
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/agents/Turn.mjs
2423
2730
  var __awaiter3 = function(thisArg, _arguments, P, generator) {
2424
2731
  function adopt(value) {
2425
2732
  return value instanceof P ? value : new P(function(resolve) {
@@ -2657,7 +2964,7 @@ _Turn_client = /* @__PURE__ */ new WeakMap(), _Turn_state = /* @__PURE__ */ new
2657
2964
  __classPrivateFieldSet(this, _Turn_state, event.state, "f");
2658
2965
  };
2659
2966
 
2660
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/agents/PreparedTurn.mjs
2967
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/agents/PreparedTurn.mjs
2661
2968
  var __awaiter4 = function(thisArg, _arguments, P, generator) {
2662
2969
  function adopt(value) {
2663
2970
  return value instanceof P ? value : new P(function(resolve) {
@@ -3012,7 +3319,7 @@ var PreparedTurn = class {
3012
3319
  };
3013
3320
  _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
3321
 
3015
- // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.2.0/node_modules/truefoundry-gateway-sdk/dist/esm/agents/AgentSession.mjs
3322
+ // ../../node_modules/.pnpm/truefoundry-gateway-sdk@0.3.1/node_modules/truefoundry-gateway-sdk/dist/esm/agents/AgentSession.mjs
3016
3323
  var __awaiter5 = function(thisArg, _arguments, P, generator) {
3017
3324
  function adopt(value) {
3018
3325
  return value instanceof P ? value : new P(function(resolve) {
@@ -3122,10 +3429,22 @@ var AgentSession = class {
3122
3429
  yield __classPrivateFieldGet3(this, _AgentSession_client, "f").agents.sessions.cancel(this.id, {}, requestOptions);
3123
3430
  });
3124
3431
  }
3432
+ /**
3433
+ * Paginated session events across turns (newest first); subscribe to a running turn for live events.
3434
+ *
3435
+ * @param opts.pageToken - Token from the previous response nextPageToken.
3436
+ * @param opts.lastTurnId - Newest turn in the listing window (initial load only). Omit to use the session last turn.
3437
+ * @param opts.limit - Page size. Default 100.
3438
+ * @param requestOptions - Overrides client timeout, retries, abortSignal, headers, queryParams.
3439
+ * @returns {Promise<core.Page<TrueFoundryGatewayApi.SessionEventItem, TrueFoundryGatewayApi.ListSessionEventsResponse>>} Paginated session events.
3440
+ */
3441
+ listEvents(opts, requestOptions) {
3442
+ return __classPrivateFieldGet3(this, _AgentSession_client, "f").agents.sessions.listEvents(this.id, opts, requestOptions);
3443
+ }
3125
3444
  };
3126
3445
  _AgentSession_client = /* @__PURE__ */ new WeakMap();
3127
3446
 
3128
- // src/bindDraftAgentSession.ts
3447
+ // src/private/bindDraftAgentSession.ts
3129
3448
  var inflightByDraftId = /* @__PURE__ */ new Map();
3130
3449
  function getGatewayFromSessionClient(client) {
3131
3450
  const internal = client;
@@ -3260,7 +3579,7 @@ function resolveTrueFoundryAgentRuntimeOptions(options) {
3260
3579
  };
3261
3580
  }
3262
3581
 
3263
- // src/useDraftAgentSpec.ts
3582
+ // src/private/useDraftAgentSpec.ts
3264
3583
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3265
3584
  var SPEC_SYNC_DEBOUNCE_MS = 400;
3266
3585
  function useDraftAgentSpec({
@@ -3280,6 +3599,29 @@ function useDraftAgentSpec({
3280
3599
  const syncGenerationRef = useRef(0);
3281
3600
  const loadedDraftIdRef = useRef(void 0);
3282
3601
  const localDirtyRef = useRef(false);
3602
+ const lastUpdatedAtRef = useRef(void 0);
3603
+ const pendingFlushRef = useRef(void 0);
3604
+ const inFlightFlushRef = useRef(void 0);
3605
+ const activeDraftIdRef = useRef(draftSessionId);
3606
+ useEffect(() => {
3607
+ const previousDraftId = activeDraftIdRef.current;
3608
+ if (previousDraftId === draftSessionId) {
3609
+ return;
3610
+ }
3611
+ activeDraftIdRef.current = draftSessionId;
3612
+ syncGenerationRef.current++;
3613
+ if (syncTimeoutRef.current != null) {
3614
+ clearTimeout(syncTimeoutRef.current);
3615
+ syncTimeoutRef.current = void 0;
3616
+ }
3617
+ pendingFlushRef.current = void 0;
3618
+ inFlightFlushRef.current = void 0;
3619
+ lastUpdatedAtRef.current = void 0;
3620
+ setIsSpecSyncing(false);
3621
+ if (previousDraftId != null) {
3622
+ localDirtyRef.current = false;
3623
+ }
3624
+ }, [draftSessionId]);
3283
3625
  useEffect(() => {
3284
3626
  if (!enabled || draftBridge == null) {
3285
3627
  return;
@@ -3328,10 +3670,11 @@ function useDraftAgentSpec({
3328
3670
  }
3329
3671
  setIsSpecSyncing(true);
3330
3672
  try {
3331
- await draftBridge.syncAgentSpec(draftId, spec);
3673
+ const updatedAt = await draftBridge.syncAgentSpec(draftId, spec);
3332
3674
  if (generation !== syncGenerationRef.current) {
3333
3675
  return;
3334
3676
  }
3677
+ lastUpdatedAtRef.current = updatedAt || (/* @__PURE__ */ new Date()).toISOString();
3335
3678
  setSpecError(null);
3336
3679
  onAgentSpecChange?.(spec);
3337
3680
  } catch (error) {
@@ -3353,14 +3696,47 @@ function useDraftAgentSpec({
3353
3696
  clearTimeout(syncTimeoutRef.current);
3354
3697
  }
3355
3698
  const generation = ++syncGenerationRef.current;
3699
+ const flush = () => {
3700
+ pendingFlushRef.current = void 0;
3701
+ syncTimeoutRef.current = void 0;
3702
+ const promise = flushSpecSync(draftId, spec, generation).finally(() => {
3703
+ if (inFlightFlushRef.current === promise) {
3704
+ inFlightFlushRef.current = void 0;
3705
+ }
3706
+ });
3707
+ inFlightFlushRef.current = promise;
3708
+ return promise;
3709
+ };
3710
+ pendingFlushRef.current = flush;
3356
3711
  syncTimeoutRef.current = setTimeout(() => {
3357
- void flushSpecSync(draftId, spec, generation);
3712
+ void flush();
3358
3713
  }, SPEC_SYNC_DEBOUNCE_MS);
3359
3714
  },
3360
3715
  [flushSpecSync]
3361
3716
  );
3362
3717
  const scheduleSpecSyncRef = useRef(scheduleSpecSync);
3363
3718
  scheduleSpecSyncRef.current = scheduleSpecSync;
3719
+ const flushPendingSpecSyncNow = useCallback(async () => {
3720
+ if (syncTimeoutRef.current != null) {
3721
+ clearTimeout(syncTimeoutRef.current);
3722
+ syncTimeoutRef.current = void 0;
3723
+ }
3724
+ const pending = pendingFlushRef.current;
3725
+ if (pending != null) {
3726
+ pendingFlushRef.current = void 0;
3727
+ await pending();
3728
+ return;
3729
+ }
3730
+ if (inFlightFlushRef.current != null) {
3731
+ await inFlightFlushRef.current;
3732
+ }
3733
+ }, []);
3734
+ const takeTurnHeaderTimestamp = useCallback(async () => {
3735
+ await flushPendingSpecSyncNow();
3736
+ const updatedAt = lastUpdatedAtRef.current;
3737
+ lastUpdatedAtRef.current = void 0;
3738
+ return updatedAt;
3739
+ }, [flushPendingSpecSyncNow]);
3364
3740
  useEffect(
3365
3741
  () => () => {
3366
3742
  if (syncTimeoutRef.current != null) {
@@ -3389,7 +3765,8 @@ function useDraftAgentSpec({
3389
3765
  draftSessionId: enabled ? draftSessionId : void 0,
3390
3766
  isSpecSyncing: enabled ? isSpecSyncing : false,
3391
3767
  specError: enabled ? specError : null,
3392
- updateAgentSpec
3768
+ updateAgentSpec,
3769
+ takeTurnHeaderTimestamp
3393
3770
  }),
3394
3771
  [
3395
3772
  agentSpec,
@@ -3397,6 +3774,7 @@ function useDraftAgentSpec({
3397
3774
  enabled,
3398
3775
  isSpecSyncing,
3399
3776
  specError,
3777
+ takeTurnHeaderTimestamp,
3400
3778
  updateAgentSpec
3401
3779
  ]
3402
3780
  );
@@ -3408,11 +3786,13 @@ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMem
3408
3786
 
3409
3787
  // src/loadSessionSnapshot.ts
3410
3788
  var inflightBySessionId2 = /* @__PURE__ */ new Map();
3411
- function loadSessionSnapshot(client, sessionId, concurrency, sessionOptions) {
3789
+ function loadSessionSnapshot(client, sessionId, sessionOptions, onProgress) {
3412
3790
  const cacheKey = sessionOptions?.draftGateway != null ? `draft:${sessionId}` : sessionId;
3413
3791
  let inflight = inflightBySessionId2.get(cacheKey);
3414
3792
  if (inflight == null) {
3415
- inflight = getSession(client, sessionId, sessionOptions).then((session) => buildSnapshotFromSession(session, concurrency)).finally(() => {
3793
+ inflight = getSession(client, sessionId, sessionOptions).then(
3794
+ (session) => buildSnapshotFromSessionEvents(session, onProgress)
3795
+ ).finally(() => {
3416
3796
  if (inflightBySessionId2.get(cacheKey) === inflight) {
3417
3797
  inflightBySessionId2.delete(cacheKey);
3418
3798
  }
@@ -3479,7 +3859,13 @@ async function* streamTurnContent(session, foldState, options, abortSignal, grou
3479
3859
  }
3480
3860
  try {
3481
3861
  yield* streamTurnEvents(
3482
- turn.execute({ stream: true }, { abortSignal }),
3862
+ turn.execute(
3863
+ { stream: true },
3864
+ {
3865
+ abortSignal,
3866
+ ...options.headers != null ? { headers: options.headers } : {}
3867
+ }
3868
+ ),
3483
3869
  foldState,
3484
3870
  groupRootBaseline
3485
3871
  );
@@ -3631,16 +4017,6 @@ async function resolveActiveSessionId(remoteId, resolveConversationSessionId) {
3631
4017
  }
3632
4018
  return remoteId;
3633
4019
  }
3634
- function findTurnIndex(snapshot, turnId) {
3635
- const committedIndex = snapshot.turns.findIndex((turn) => turn.id === turnId);
3636
- if (committedIndex !== -1) {
3637
- return committedIndex;
3638
- }
3639
- if (snapshot.pendingUser?.turnId === turnId) {
3640
- return snapshot.turns.length;
3641
- }
3642
- throw new Error(`Turn ${turnId} not found in session snapshot`);
3643
- }
3644
4020
  function resolveTurnInput(snapshot, turnId) {
3645
4021
  const turnRecord = snapshot.turns.find((turn) => turn.id === turnId);
3646
4022
  if (turnRecord?.input != null) {
@@ -3654,11 +4030,13 @@ function resolveTurnInput(snapshot, turnId) {
3654
4030
  function useTrueFoundryAgentMessages({
3655
4031
  client,
3656
4032
  sessionId,
4033
+ isMain,
3657
4034
  listEventsConcurrency,
3658
4035
  onError,
3659
4036
  initializeSession,
3660
4037
  resolveConversationSessionId,
3661
- draftGateway
4038
+ draftGateway,
4039
+ getTurnHeaders
3662
4040
  }) {
3663
4041
  const sessionOptions = useMemo2(
3664
4042
  () => draftGateway != null ? { draftGateway } : void 0,
@@ -3667,13 +4045,25 @@ function useTrueFoundryAgentMessages({
3667
4045
  const [snapshot, setSnapshot] = useState2(createEmptySessionSnapshot);
3668
4046
  const [isRunning, setIsRunning] = useState2(false);
3669
4047
  const [isLoading, setIsLoading] = useState2(false);
4048
+ const [isLoadingOlderHistory, setIsLoadingOlderHistory] = useState2(false);
4049
+ const [loadRetryTrigger, setLoadRetryTrigger] = useState2(0);
3670
4050
  const snapshotRef = useRef2(snapshot);
3671
4051
  snapshotRef.current = snapshot;
4052
+ const loadOlderInflightRef = useRef2(null);
4053
+ const onErrorRef = useRef2(onError);
4054
+ onErrorRef.current = onError;
4055
+ const resolveConversationSessionIdRef = useRef2(resolveConversationSessionId);
4056
+ resolveConversationSessionIdRef.current = resolveConversationSessionId;
4057
+ const initializeSessionRef = useRef2(initializeSession);
4058
+ initializeSessionRef.current = initializeSession;
4059
+ const getTurnHeadersRef = useRef2(getTurnHeaders);
4060
+ getTurnHeadersRef.current = getTurnHeaders;
3672
4061
  const createdAtByMessageIdRef = useRef2(/* @__PURE__ */ new Map());
3673
4062
  const abortControllerRef = useRef2(null);
3674
4063
  const activeRunRef = useRef2(null);
3675
4064
  const runningTurnRef = useRef2(void 0);
3676
4065
  const loadGenerationRef = useRef2(0);
4066
+ const streamGenerationRef = useRef2(0);
3677
4067
  const lazilyCreatedSessionIdRef = useRef2(void 0);
3678
4068
  const projectOptions = useMemo2(
3679
4069
  () => ({
@@ -3693,41 +4083,62 @@ function useTrueFoundryAgentMessages({
3693
4083
  () => projectSessionMessages(snapshot, projectOptions),
3694
4084
  [snapshot, projectOptions]
3695
4085
  );
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
4086
  const runStream = useCallback2(
3711
4087
  (createStream, turnId, isContinuation) => {
4088
+ const streamGeneration = ++streamGenerationRef.current;
3712
4089
  abortControllerRef.current?.abort();
3713
4090
  const abortController = new AbortController();
3714
4091
  abortControllerRef.current = abortController;
3715
4092
  setIsRunning(true);
3716
4093
  const run = (async () => {
4094
+ let pendingStreamUpdate = null;
4095
+ let streamUpdateRaf = null;
4096
+ const flushPendingStreamUpdate = () => {
4097
+ streamUpdateRaf = null;
4098
+ const pending = pendingStreamUpdate;
4099
+ pendingStreamUpdate = null;
4100
+ if (pending == null || streamGeneration !== streamGenerationRef.current) {
4101
+ return;
4102
+ }
4103
+ const { update, turnId: pendingTurnId, isContinuation: pendingIsContinuation } = pending;
4104
+ setSnapshot(
4105
+ (prev) => replaceSessionSnapshot(prev, {
4106
+ activeStream: {
4107
+ turnId: pendingTurnId,
4108
+ update,
4109
+ isContinuation: pendingIsContinuation
4110
+ }
4111
+ })
4112
+ );
4113
+ };
4114
+ const applyStreamUpdate = (update) => {
4115
+ pendingStreamUpdate = { update, turnId, isContinuation };
4116
+ if (streamUpdateRaf == null) {
4117
+ streamUpdateRaf = requestAnimationFrame(flushPendingStreamUpdate);
4118
+ }
4119
+ };
3717
4120
  try {
3718
4121
  for await (const update of createStream(abortController.signal)) {
3719
4122
  if (abortController.signal.aborted) {
3720
4123
  return;
3721
4124
  }
3722
- applyStreamUpdate(update, turnId, isContinuation);
4125
+ applyStreamUpdate(update);
3723
4126
  }
3724
4127
  } catch (error) {
3725
4128
  if (error instanceof Error && error.name === "AbortError") {
3726
4129
  return;
3727
4130
  }
3728
- onError?.(error);
4131
+ onErrorRef.current?.(error);
3729
4132
  throw error;
3730
4133
  } finally {
4134
+ if (streamUpdateRaf != null) {
4135
+ cancelAnimationFrame(streamUpdateRaf);
4136
+ streamUpdateRaf = null;
4137
+ }
4138
+ if (streamGeneration !== streamGenerationRef.current) {
4139
+ return;
4140
+ }
4141
+ flushPendingStreamUpdate();
3731
4142
  if (abortControllerRef.current === abortController) {
3732
4143
  abortControllerRef.current = null;
3733
4144
  }
@@ -3758,7 +4169,7 @@ function useTrueFoundryAgentMessages({
3758
4169
  });
3759
4170
  return run;
3760
4171
  },
3761
- [applyStreamUpdate, onError]
4172
+ [onError]
3762
4173
  );
3763
4174
  const load = useCallback2(async () => {
3764
4175
  if (sessionId == null) {
@@ -3766,22 +4177,35 @@ function useTrueFoundryAgentMessages({
3766
4177
  setSnapshot(createEmptySessionSnapshot());
3767
4178
  return;
3768
4179
  }
4180
+ if (isMain === false) return;
4181
+ if (lazilyCreatedSessionIdRef.current != null && sessionId !== lazilyCreatedSessionIdRef.current) {
4182
+ lazilyCreatedSessionIdRef.current = void 0;
4183
+ }
3769
4184
  if (sessionId === lazilyCreatedSessionIdRef.current) {
3770
4185
  return;
3771
4186
  }
3772
4187
  const generation = ++loadGenerationRef.current;
4188
+ ++streamGenerationRef.current;
3773
4189
  abortControllerRef.current?.abort();
4190
+ loadOlderInflightRef.current = null;
4191
+ createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
4192
+ setSnapshot(createEmptySessionSnapshot());
3774
4193
  setIsLoading(true);
4194
+ setIsLoadingOlderHistory(false);
3775
4195
  try {
3776
4196
  const conversationSessionId = await resolveActiveSessionId(
3777
4197
  sessionId,
3778
- resolveConversationSessionId
4198
+ resolveConversationSessionIdRef.current
3779
4199
  );
3780
4200
  const loadedSnapshot = await loadSessionSnapshot(
3781
4201
  client,
3782
4202
  conversationSessionId,
3783
- listEventsConcurrency,
3784
- sessionOptions
4203
+ sessionOptions,
4204
+ (snap) => {
4205
+ if (generation === loadGenerationRef.current) {
4206
+ setSnapshot(snap);
4207
+ }
4208
+ }
3785
4209
  );
3786
4210
  if (generation !== loadGenerationRef.current) {
3787
4211
  return;
@@ -3789,30 +4213,33 @@ function useTrueFoundryAgentMessages({
3789
4213
  createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
3790
4214
  setSnapshot(loadedSnapshot);
3791
4215
  runningTurnRef.current = loadedSnapshot.runningTurn;
4216
+ setIsLoading(false);
3792
4217
  if (loadedSnapshot.runningTurn != null) {
3793
4218
  const turn = loadedSnapshot.runningTurn;
3794
4219
  const isContinuation = !extractTurnUserText(turn.input);
3795
- await runStream(
4220
+ void runStream(
3796
4221
  (signal) => resumeTurnStream(
3797
4222
  turn,
3798
- snapshotRef.current.fold,
4223
+ loadedSnapshot.fold,
3799
4224
  signal,
3800
4225
  void 0,
3801
- snapshotRef.current.groupRootBaseline
4226
+ loadedSnapshot.groupRootBaseline
3802
4227
  ),
3803
4228
  turn.id,
3804
4229
  isContinuation
3805
- );
4230
+ ).catch(() => void 0);
3806
4231
  }
3807
4232
  } catch (error) {
3808
- onError?.(error);
4233
+ if (generation === loadGenerationRef.current) {
4234
+ onErrorRef.current?.(error);
4235
+ }
3809
4236
  throw error;
3810
4237
  } finally {
3811
4238
  if (generation === loadGenerationRef.current) {
3812
4239
  setIsLoading(false);
3813
4240
  }
3814
4241
  }
3815
- }, [client, listEventsConcurrency, onError, resolveConversationSessionId, runStream, sessionId, sessionOptions]);
4242
+ }, [client, runStream, sessionId, sessionOptions, loadRetryTrigger, isMain]);
3816
4243
  useEffect2(() => {
3817
4244
  void load().catch(() => void 0);
3818
4245
  }, [load]);
@@ -3820,18 +4247,20 @@ function useTrueFoundryAgentMessages({
3820
4247
  async (options) => {
3821
4248
  let activeSessionId = sessionId;
3822
4249
  if (activeSessionId == null) {
3823
- if (initializeSession == null) {
4250
+ if (initializeSessionRef.current == null) {
3824
4251
  throw new Error("Cannot send a turn without an active session.");
3825
4252
  }
3826
- const { remoteId } = await initializeSession();
4253
+ const { remoteId } = await initializeSessionRef.current();
3827
4254
  activeSessionId = remoteId;
3828
4255
  lazilyCreatedSessionIdRef.current = remoteId;
3829
4256
  }
3830
4257
  const conversationSessionId = await resolveActiveSessionId(
3831
4258
  activeSessionId,
3832
- resolveConversationSessionId
4259
+ resolveConversationSessionIdRef.current
3833
4260
  );
3834
4261
  const session = await getSession(client, conversationSessionId, sessionOptions);
4262
+ const turnHeaders = await getTurnHeadersRef.current?.();
4263
+ const streamHeaders = turnHeaders != null ? { headers: turnHeaders } : {};
3835
4264
  const isContinuation = "inputs" in options || "resumeMcpAuth" in options && options.resumeMcpAuth === true;
3836
4265
  const continuationTurnId = snapshotRef.current.activeStream?.turnId;
3837
4266
  const turnId = isContinuation && continuationTurnId != null ? continuationTurnId : generateId();
@@ -3841,29 +4270,48 @@ function useTrueFoundryAgentMessages({
3841
4270
  options.inputs
3842
4271
  );
3843
4272
  }
3844
- setSnapshot(
3845
- (prev) => commitActiveStream(
3846
- prev,
3847
- "inputs" in options ? options.inputs : void 0
3848
- )
3849
- );
4273
+ const branchBase = "userMessage" in options ? options.branchFromSnapshot : void 0;
3850
4274
  let groupRootBaseline;
3851
- if ("userMessage" in options) {
3852
- const rootBucket = snapshotRef.current.fold.threads.get(ROOT_THREAD_ID);
4275
+ if (branchBase != null && "userMessage" in options) {
4276
+ const rootBucket = branchBase.fold.threads.get(ROOT_THREAD_ID);
3853
4277
  groupRootBaseline = [...rootBucket?.modelMessageIds ?? []];
4278
+ const nextSnapshot = replaceSessionSnapshot(branchBase, {
4279
+ pendingUser: {
4280
+ turnId,
4281
+ content: options.userMessage,
4282
+ createdAt: /* @__PURE__ */ new Date()
4283
+ },
4284
+ activeStream: void 0,
4285
+ groupRootBaseline
4286
+ });
4287
+ snapshotRef.current = nextSnapshot;
4288
+ setSnapshot(nextSnapshot);
4289
+ } else {
3854
4290
  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
- })
4291
+ (prev) => commitActiveStream(
4292
+ prev,
4293
+ "inputs" in options ? options.inputs : void 0
4294
+ )
3864
4295
  );
3865
- } else {
3866
- groupRootBaseline = snapshotRef.current.groupRootBaseline ?? computeGroupRootBaseline(snapshotRef.current.turns);
4296
+ if ("userMessage" in options) {
4297
+ const rootBucket = snapshotRef.current.fold.threads.get(ROOT_THREAD_ID);
4298
+ groupRootBaseline = [...rootBucket?.modelMessageIds ?? []];
4299
+ setSnapshot((prev) => {
4300
+ const next = replaceSessionSnapshot(prev, {
4301
+ pendingUser: {
4302
+ turnId,
4303
+ content: options.userMessage,
4304
+ createdAt: /* @__PURE__ */ new Date()
4305
+ },
4306
+ activeStream: void 0,
4307
+ groupRootBaseline
4308
+ });
4309
+ snapshotRef.current = next;
4310
+ return next;
4311
+ });
4312
+ } else {
4313
+ groupRootBaseline = snapshotRef.current.groupRootBaseline ?? computeGroupRootBaseline(snapshotRef.current.turns);
4314
+ }
3867
4315
  }
3868
4316
  await runStream(
3869
4317
  (signal) => {
@@ -3871,7 +4319,7 @@ function useTrueFoundryAgentMessages({
3871
4319
  return streamTurnContent(
3872
4320
  session,
3873
4321
  snapshotRef.current.fold,
3874
- { inputs: options.inputs },
4322
+ { inputs: options.inputs, ...streamHeaders },
3875
4323
  signal,
3876
4324
  groupRootBaseline
3877
4325
  );
@@ -3880,7 +4328,7 @@ function useTrueFoundryAgentMessages({
3880
4328
  return streamTurnContent(
3881
4329
  session,
3882
4330
  snapshotRef.current.fold,
3883
- { resumeMcpAuth: true },
4331
+ { resumeMcpAuth: true, ...streamHeaders },
3884
4332
  signal,
3885
4333
  groupRootBaseline
3886
4334
  );
@@ -3890,7 +4338,8 @@ function useTrueFoundryAgentMessages({
3890
4338
  snapshotRef.current.fold,
3891
4339
  {
3892
4340
  userMessage: options.userMessage,
3893
- ...options.previousTurnId !== void 0 ? { previousTurnId: options.previousTurnId } : {}
4341
+ ...options.previousTurnId !== void 0 ? { previousTurnId: options.previousTurnId } : {},
4342
+ ...streamHeaders
3894
4343
  },
3895
4344
  signal,
3896
4345
  groupRootBaseline
@@ -3900,7 +4349,7 @@ function useTrueFoundryAgentMessages({
3900
4349
  isContinuation
3901
4350
  );
3902
4351
  },
3903
- [client, initializeSession, resolveConversationSessionId, runStream, sessionId, sessionOptions]
4352
+ [client, runStream, sessionId, sessionOptions]
3904
4353
  );
3905
4354
  const cancel = useCallback2(async () => {
3906
4355
  if (sessionId == null) {
@@ -3909,12 +4358,12 @@ function useTrueFoundryAgentMessages({
3909
4358
  }
3910
4359
  const conversationSessionId = await resolveActiveSessionId(
3911
4360
  sessionId,
3912
- resolveConversationSessionId
4361
+ resolveConversationSessionIdRef.current
3913
4362
  );
3914
4363
  const session = await getSession(client, conversationSessionId, sessionOptions);
3915
4364
  await session.cancel().catch(() => void 0);
3916
4365
  await activeRunRef.current?.catch(() => void 0);
3917
- }, [client, resolveConversationSessionId, sessionId, sessionOptions]);
4366
+ }, [client, sessionId, sessionOptions]);
3918
4367
  const isRunningRef = useRef2(isRunning);
3919
4368
  isRunningRef.current = isRunning;
3920
4369
  const trySendCollectedRequiredActions = useCallback2(
@@ -3929,10 +4378,10 @@ function useTrueFoundryAgentMessages({
3929
4378
  }
3930
4379
  const inputs = collectRequiredActionInputs(paused);
3931
4380
  if (inputs.length > 0) {
3932
- void sendTurn({ inputs }).catch((error) => onError?.(error));
4381
+ void sendTurn({ inputs }).catch((error) => onErrorRef.current?.(error));
3933
4382
  }
3934
4383
  },
3935
- [onError, projectOptions, sendTurn]
4384
+ [projectOptions, sendTurn]
3936
4385
  );
3937
4386
  const respondToToolApproval = useCallback2(
3938
4387
  (response) => {
@@ -3994,34 +4443,34 @@ function useTrueFoundryAgentMessages({
3994
4443
  }
3995
4444
  const committed = commitActiveStream(snapshotRef.current);
3996
4445
  setSnapshot(committed);
3997
- const turnIndex = findTurnIndex(committed, turnId);
3998
4446
  await cancel();
3999
4447
  const conversationSessionId = await resolveActiveSessionId(
4000
4448
  activeSessionId,
4001
- resolveConversationSessionId
4449
+ resolveConversationSessionIdRef.current
4002
4450
  );
4003
4451
  const session = await getSession(client, conversationSessionId, sessionOptions);
4004
- const previousTurnId = await resolveGatewayBranchPreviousTurnId(
4452
+ const previousTurnId = await resolveGatewayBranchPreviousTurnIdForTurn(
4005
4453
  session,
4006
- turnIndex
4454
+ turnId
4007
4455
  );
4008
- const rewound = await buildSnapshotBeforeTurnIndex(
4456
+ const rewound = await buildSnapshotBeforeTurn(
4009
4457
  session,
4010
- turnIndex,
4458
+ turnId,
4011
4459
  listEventsConcurrency
4012
4460
  );
4013
4461
  createdAtByMessageIdRef.current = /* @__PURE__ */ new Map();
4462
+ snapshotRef.current = rewound;
4014
4463
  setSnapshot(rewound);
4015
4464
  await sendTurn({
4016
4465
  userMessage,
4017
- previousTurnId
4466
+ previousTurnId,
4467
+ branchFromSnapshot: rewound
4018
4468
  });
4019
4469
  },
4020
4470
  [
4021
4471
  cancel,
4022
4472
  client,
4023
4473
  listEventsConcurrency,
4024
- resolveConversationSessionId,
4025
4474
  sendTurn,
4026
4475
  sessionId,
4027
4476
  sessionOptions
@@ -4054,11 +4503,68 @@ function useTrueFoundryAgentMessages({
4054
4503
  },
4055
4504
  [branchFromTurn]
4056
4505
  );
4506
+ const retryLoad = useCallback2(() => {
4507
+ setLoadRetryTrigger((n) => n + 1);
4508
+ }, []);
4509
+ const hasOlderHistory = snapshot.historyPagination?.hasOlder === true;
4510
+ const loadOlderHistory = useCallback2(async () => {
4511
+ if (sessionId == null || isMain === false) {
4512
+ return;
4513
+ }
4514
+ if (loadOlderInflightRef.current != null) {
4515
+ return loadOlderInflightRef.current;
4516
+ }
4517
+ const current = snapshotRef.current;
4518
+ if (current.historyPagination?.hasOlder !== true) {
4519
+ return;
4520
+ }
4521
+ if (current.historyPagination.olderPageToken == null) {
4522
+ return;
4523
+ }
4524
+ const generation = loadGenerationRef.current;
4525
+ setIsLoadingOlderHistory(true);
4526
+ const run = (async () => {
4527
+ try {
4528
+ const conversationSessionId = await resolveActiveSessionId(
4529
+ sessionId,
4530
+ resolveConversationSessionIdRef.current
4531
+ );
4532
+ const session = await getSession(
4533
+ client,
4534
+ conversationSessionId,
4535
+ sessionOptions
4536
+ );
4537
+ const next = await prependOlderSessionHistory(
4538
+ session,
4539
+ snapshotRef.current
4540
+ );
4541
+ if (generation !== loadGenerationRef.current) {
4542
+ return;
4543
+ }
4544
+ setSnapshot(next);
4545
+ } catch (error) {
4546
+ if (generation === loadGenerationRef.current) {
4547
+ onErrorRef.current?.(error);
4548
+ }
4549
+ throw error;
4550
+ } finally {
4551
+ if (generation === loadGenerationRef.current) {
4552
+ setIsLoadingOlderHistory(false);
4553
+ }
4554
+ loadOlderInflightRef.current = null;
4555
+ }
4556
+ })();
4557
+ loadOlderInflightRef.current = run;
4558
+ return run;
4559
+ }, [client, isMain, sessionId, sessionOptions]);
4057
4560
  return {
4058
4561
  messages,
4059
4562
  isRunning,
4060
4563
  isLoading,
4061
- load,
4564
+ isLoadingOlderHistory,
4565
+ hasOlderHistory,
4566
+ loadOlderHistory,
4567
+ retryLoad,
4062
4568
  sendTurn,
4063
4569
  cancel,
4064
4570
  respondToToolApproval,
@@ -4088,6 +4594,9 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4088
4594
  (state) => agent.mode === "draft" ? state.threadListItem.remoteId ?? void 0 : void 0
4089
4595
  );
4090
4596
  const sessionId = useAuiState((state) => state.threadListItem.remoteId ?? void 0);
4597
+ const isMain = useAuiState(
4598
+ (state) => state.threads.mainThreadId === state.threadListItem.id
4599
+ );
4091
4600
  const draftSpec = useDraftAgentSpec({
4092
4601
  draftSessionId,
4093
4602
  draftBridge: draftBridgeRef.current,
@@ -4095,6 +4604,18 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4095
4604
  onAgentSpecChange: agent.mode === "draft" ? agent.onAgentSpecChange : void 0,
4096
4605
  onError
4097
4606
  });
4607
+ const takeTurnHeaderTimestampRef = useRef3(draftSpec.takeTurnHeaderTimestamp);
4608
+ takeTurnHeaderTimestampRef.current = draftSpec.takeTurnHeaderTimestamp;
4609
+ const getTurnHeaders = useCallback3(async () => {
4610
+ if (agent.mode !== "draft") {
4611
+ return void 0;
4612
+ }
4613
+ const updatedAt = await takeTurnHeaderTimestampRef.current();
4614
+ if (updatedAt == null) {
4615
+ return void 0;
4616
+ }
4617
+ return { [DRAFT_SESSION_LAST_UPDATED_AT_HEADER]: updatedAt };
4618
+ }, [agent.mode]);
4098
4619
  const aui = useAui();
4099
4620
  const initializeSession = useCallback3(
4100
4621
  () => aui.threadListItem().initialize(),
@@ -4106,20 +4627,26 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4106
4627
  messages,
4107
4628
  isRunning,
4108
4629
  isLoading,
4630
+ isLoadingOlderHistory,
4631
+ hasOlderHistory,
4632
+ loadOlderHistory,
4109
4633
  sendTurn,
4110
4634
  cancel,
4111
4635
  respondToToolApproval,
4112
4636
  respondToToolResponse,
4113
4637
  resumeRun,
4114
4638
  editFromTurn,
4115
- resetFromTurn
4639
+ resetFromTurn,
4640
+ retryLoad
4116
4641
  } = useTrueFoundryAgentMessages({
4117
4642
  client,
4118
4643
  sessionId,
4644
+ isMain,
4119
4645
  listEventsConcurrency,
4120
4646
  onError,
4121
4647
  initializeSession,
4122
- draftGateway: agent.mode === "draft" ? gateway : void 0
4648
+ draftGateway: agent.mode === "draft" ? gateway : void 0,
4649
+ getTurnHeaders: agent.mode === "draft" ? getTurnHeaders : void 0
4123
4650
  });
4124
4651
  if (agent.mode === "draft" && draftSpec.agentSpec != null) {
4125
4652
  pendingAgentSpecRef.current = draftSpec.agentSpec;
@@ -4187,6 +4714,10 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4187
4714
  resetFromTurn: (turnId) => resetFromTurn(turnId).catch((error) => {
4188
4715
  onError?.(error);
4189
4716
  }),
4717
+ reload: retryLoad,
4718
+ hasOlderHistory,
4719
+ isLoadingOlderHistory,
4720
+ loadOlderHistory,
4190
4721
  draft: draftExtras
4191
4722
  }),
4192
4723
  unstable_enableToolInvocations: true,
@@ -4235,32 +4766,32 @@ function useTrueFoundryAgentRuntimeImpl(options, pendingAgentSpecRef) {
4235
4766
  });
4236
4767
  }
4237
4768
  function useTrueFoundryAgentRuntime(options) {
4238
- const resolved = useMemo3(
4239
- () => resolveTrueFoundryAgentRuntimeOptions(options),
4240
- [options]
4241
- );
4769
+ const resolved = resolveTrueFoundryAgentRuntimeOptions(options);
4242
4770
  const { client, agent, gateway } = resolved;
4243
4771
  const pendingAgentSpecRef = useRef3(
4244
4772
  agent.mode === "draft" ? agent.defaultAgentSpec : void 0
4245
4773
  );
4774
+ const agentMode = agent.mode;
4775
+ const namedAgentName = agent.mode === "named" ? agent.agentName : void 0;
4246
4776
  const threadListAdapter = useMemo3(() => {
4247
- if (agent.mode === "draft") {
4777
+ if (agentMode === "draft") {
4248
4778
  if (gateway == null) {
4249
4779
  throw new Error(
4250
4780
  "Draft agent mode requires a `gateway` TrueFoundryGateway client."
4251
4781
  );
4252
4782
  }
4783
+ const draftAgent = agent;
4253
4784
  return createTrueFoundryDraftThreadListAdapter({
4254
4785
  gateway,
4255
- defaultAgentSpec: agent.defaultAgentSpec,
4256
- getAgentSpec: () => pendingAgentSpecRef.current ?? agent.defaultAgentSpec
4786
+ defaultAgentSpec: draftAgent.defaultAgentSpec,
4787
+ getAgentSpec: () => pendingAgentSpecRef.current ?? draftAgent.defaultAgentSpec
4257
4788
  });
4258
4789
  }
4259
4790
  return createTrueFoundryThreadListAdapter({
4260
4791
  client,
4261
- agentName: agent.agentName
4792
+ agentName: namedAgentName
4262
4793
  });
4263
- }, [agent, client, gateway]);
4794
+ }, [agentMode, namedAgentName, client, gateway]);
4264
4795
  return useRemoteThreadListRuntime({
4265
4796
  allowNesting: true,
4266
4797
  adapter: threadListAdapter,
@@ -4331,6 +4862,23 @@ var useTrueFoundryCancel = () => {
4331
4862
  const aui = useAui2();
4332
4863
  return () => trueFoundryExtras.get(aui).cancel();
4333
4864
  };
4865
+ var useTrueFoundryReload = () => {
4866
+ const aui = useAui2();
4867
+ return () => trueFoundryExtras.get(aui).reload();
4868
+ };
4869
+ var useTrueFoundryHistoryPagination = () => {
4870
+ const extras = trueFoundryExtras.use((e) => e, void 0);
4871
+ return useMemo4(
4872
+ () => ({
4873
+ hasOlderHistory: extras?.hasOlderHistory ?? false,
4874
+ isLoadingOlderHistory: extras?.isLoadingOlderHistory ?? false,
4875
+ loadOlderHistory: extras?.loadOlderHistory ?? (async () => {
4876
+ throw new Error("TrueFoundry runtime is not ready yet");
4877
+ })
4878
+ }),
4879
+ [extras]
4880
+ );
4881
+ };
4334
4882
  var useTrueFoundryResetFromTurn = () => {
4335
4883
  const aui = useAui2();
4336
4884
  return (turnId) => trueFoundryExtras.get(aui).resetFromTurn(turnId);
@@ -4428,7 +4976,9 @@ export {
4428
4976
  useTrueFoundryApprovals,
4429
4977
  useTrueFoundryCancel,
4430
4978
  useTrueFoundryDownloadSandboxFile,
4979
+ useTrueFoundryHistoryPagination,
4431
4980
  useTrueFoundryMcpAuth,
4981
+ useTrueFoundryReload,
4432
4982
  useTrueFoundryResetFromTurn,
4433
4983
  useTrueFoundryRespondToToolApproval,
4434
4984
  useTrueFoundryRespondToToolResponse,