@agent-native/core 0.207.0 → 0.207.1-nightly-20261012011809

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 (61) hide show
  1. package/dist/agent/thread-data-builder.d.ts +1 -0
  2. package/dist/agent/thread-data-builder.js +220 -45
  3. package/dist/agent/thread-message-projection.d.ts +3 -0
  4. package/dist/agent/thread-message-projection.js +136 -3
  5. package/dist/automation/index.d.ts +1 -1
  6. package/dist/chat-threads/store.d.ts +1 -0
  7. package/dist/chat-threads/store.js +14 -4
  8. package/dist/cli/connect.d.ts +1 -0
  9. package/dist/cli/connect.js +8 -3
  10. package/dist/client/chat/agentkit-agent-native.js +151 -77
  11. package/dist/client/chat/runtime.js +61 -2
  12. package/dist/client/onboarding/first-run-status.js +0 -4
  13. package/dist/collab/routes.d.ts +1 -1
  14. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  15. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  16. package/dist/mcp/agent-connected.d.ts +13 -0
  17. package/dist/mcp/agent-connected.js +63 -0
  18. package/dist/mcp/connect-app-label.d.ts +11 -0
  19. package/dist/mcp/connect-app-label.js +33 -0
  20. package/dist/mcp/connect-route.js +41 -33
  21. package/dist/mcp/connect-store.d.ts +6 -1
  22. package/dist/mcp/connect-store.js +10 -2
  23. package/dist/mcp/oauth-route.js +17 -5
  24. package/dist/mcp/oauth-store.d.ts +2 -0
  25. package/dist/mcp/oauth-store.js +5 -1
  26. package/dist/notifications/routes.d.ts +1 -1
  27. package/dist/org/index.d.ts +2 -2
  28. package/dist/progress/routes.d.ts +1 -1
  29. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  30. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  31. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  32. package/dist/resources/handlers.d.ts +1 -1
  33. package/dist/secrets/routes.d.ts +5 -5
  34. package/dist/server/agent-chat-plugin.js +1 -1
  35. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  36. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  37. package/dist/server/core-routes-plugin.d.ts +1 -1
  38. package/dist/shared/mcp-connect-content.d.ts +1 -0
  39. package/dist/shared/mcp-connect-content.js +3 -0
  40. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  41. package/dist/tracking/registry.d.ts +1 -0
  42. package/dist/tracking/registry.js +1 -1
  43. package/dist/triggers/actions/manage-automation.d.ts +7 -7
  44. package/docs/content/agent-native-toolkit.mdx +42 -38
  45. package/docs/content/components.mdx +29 -18
  46. package/docs/content/notifications.mdx +1 -1
  47. package/docs/content/progress.mdx +1 -1
  48. package/docs/content/sharing.mdx +1 -1
  49. package/docs/content/toolkit-agent-ux.mdx +17 -2
  50. package/docs/content/toolkit-command-navigation.mdx +4 -2
  51. package/docs/content/toolkit-comments-review.mdx +1 -1
  52. package/docs/content/toolkit-context-knowledge.mdx +14 -16
  53. package/docs/content/toolkit-editors-canvases.mdx +24 -7
  54. package/docs/content/toolkit-history.mdx +13 -26
  55. package/docs/content/toolkit-org-team.mdx +4 -4
  56. package/docs/content/toolkit-resources.mdx +3 -1
  57. package/docs/content/toolkit-settings.mdx +1 -1
  58. package/docs/content/toolkit-setup-connections.mdx +1 -1
  59. package/docs/content/toolkit-sharing.mdx +6 -6
  60. package/docs/content/toolkit-ui.mdx +11 -0
  61. package/package.json +4 -4
@@ -105,6 +105,7 @@ export interface MergeThreadDataOptions {
105
105
  preserveExistingQueuedMessages?: boolean;
106
106
  preserveExistingTopLevelKeys?: boolean;
107
107
  onAnnotationConflict?: (conflict: ThreadAnnotationSnapshotConflict) => void;
108
+ trustedRunSequenceResetIds?: ReadonlySet<string>;
108
109
  }
109
110
  export interface ThreadAnnotationSnapshotConflict {
110
111
  messageId: string;
@@ -1582,13 +1582,18 @@ export function foldThreadRunSuggestions(repo, run) {
1582
1582
  ? previous.runs
1583
1583
  : [];
1584
1584
  const oldRun = runs.find((entry) => entry.id === run.runId);
1585
- const previousSequence = oldRun &&
1586
- Number.isSafeInteger(oldRun.lastSequence) &&
1587
- oldRun.lastSequence >= 0
1588
- ? oldRun.lastSequence
1589
- : 0;
1590
- // Runtime event sequence numbers and AgentKit protocol cursors are separate.
1591
- const lastSequence = previousSequence;
1585
+ const committedSnapshots = previous._eventRunSnapshotCommits?.[run.runId];
1586
+ const protocolSequences = [
1587
+ previous._eventRunWatermarks?.[run.runId],
1588
+ ...(Array.isArray(committedSnapshots)
1589
+ ? committedSnapshots.map((snapshot) => snapshot?.lastSequence)
1590
+ : []),
1591
+ ];
1592
+ const lastSequence = protocolSequences.reduce((maximum, sequence) => typeof sequence === "number" &&
1593
+ Number.isSafeInteger(sequence) &&
1594
+ sequence >= 0
1595
+ ? Math.max(maximum, sequence)
1596
+ : maximum, 0);
1592
1597
  const snapshot = {
1593
1598
  ...oldRun,
1594
1599
  id: run.runId,
@@ -1653,6 +1658,7 @@ function mergeAgentKitEvents(existing, incoming, replacedRunIds = new Set(), sta
1653
1658
  Number.isFinite(merged[index].sequence)
1654
1659
  ? merged[index].sequence
1655
1660
  : Number.NaN,
1661
+ sequenceEnd: getAgentKitEventSequenceEnd(merged[index]),
1656
1662
  }));
1657
1663
  runEvents.sort((left, right) => {
1658
1664
  if (Number.isFinite(left.sequence) &&
@@ -1662,12 +1668,142 @@ function mergeAgentKitEvents(existing, incoming, replacedRunIds = new Set(), sta
1662
1668
  }
1663
1669
  return left.order - right.order;
1664
1670
  });
1665
- runPositions.forEach((index, order) => {
1666
- merged[index] = { ...runEvents[order].event, sequence: order + 1 };
1667
- });
1671
+ let nextSequence = 1;
1672
+ for (const [order, { event, sequenceEnd, sequence },] of runEvents.entries()) {
1673
+ const index = runPositions[order];
1674
+ if (index === undefined)
1675
+ continue;
1676
+ const span = Number.isSafeInteger(sequenceEnd) &&
1677
+ Number.isSafeInteger(sequence) &&
1678
+ sequenceEnd >= sequence
1679
+ ? sequenceEnd - sequence + 1
1680
+ : 1;
1681
+ if (event?.type === "x-agent-native.persisted-event-omission-range") {
1682
+ const payload = event.payload &&
1683
+ typeof event.payload === "object" &&
1684
+ !Array.isArray(event.payload)
1685
+ ? event.payload
1686
+ : {};
1687
+ merged[index] = {
1688
+ ...event,
1689
+ sequence: nextSequence,
1690
+ payload: {
1691
+ ...payload,
1692
+ lastSequence: nextSequence + span - 1,
1693
+ },
1694
+ };
1695
+ }
1696
+ else {
1697
+ merged[index] = { ...event, sequence: nextSequence };
1698
+ }
1699
+ nextSequence += span;
1700
+ }
1668
1701
  }
1669
1702
  return merged;
1670
1703
  }
1704
+ function mergeAgentKitEventSnapshotBatch(existing, incoming) {
1705
+ const merged = Array.isArray(existing) ? [...existing] : [];
1706
+ const positions = new Map();
1707
+ merged.forEach((event, index) => {
1708
+ if (typeof event?.id === "string" && !positions.has(event.id)) {
1709
+ positions.set(event.id, index);
1710
+ }
1711
+ });
1712
+ for (const event of Array.isArray(incoming) ? incoming : []) {
1713
+ if (typeof event?.id !== "string") {
1714
+ merged.push(event);
1715
+ continue;
1716
+ }
1717
+ const index = positions.get(event.id);
1718
+ if (index === undefined) {
1719
+ positions.set(event.id, merged.length);
1720
+ merged.push(event);
1721
+ }
1722
+ else {
1723
+ merged[index] = event;
1724
+ }
1725
+ }
1726
+ return merged
1727
+ .map((event, order) => ({
1728
+ event,
1729
+ order,
1730
+ sequence: typeof event?.sequence === "number" && Number.isFinite(event.sequence)
1731
+ ? event.sequence
1732
+ : Number.NaN,
1733
+ }))
1734
+ .sort((left, right) => {
1735
+ if (Number.isFinite(left.sequence) &&
1736
+ Number.isFinite(right.sequence) &&
1737
+ left.sequence !== right.sequence) {
1738
+ return left.sequence - right.sequence;
1739
+ }
1740
+ return left.order - right.order;
1741
+ })
1742
+ .map(({ event }) => event);
1743
+ }
1744
+ const MAX_AGENT_NATIVE_PERSISTED_EVENT_OMISSION_RANGE = 256;
1745
+ function getAgentKitEventSequence(event) {
1746
+ if (!event || typeof event !== "object" || Array.isArray(event)) {
1747
+ return undefined;
1748
+ }
1749
+ const sequence = event.sequence;
1750
+ return typeof sequence === "number" && Number.isSafeInteger(sequence)
1751
+ ? sequence
1752
+ : undefined;
1753
+ }
1754
+ function getAgentKitEventSequenceEnd(event) {
1755
+ const sequence = getAgentKitEventSequence(event);
1756
+ if (sequence === undefined)
1757
+ return undefined;
1758
+ if (!event ||
1759
+ typeof event !== "object" ||
1760
+ event.type !==
1761
+ "x-agent-native.persisted-event-omission-range") {
1762
+ return sequence;
1763
+ }
1764
+ const payload = event.payload;
1765
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1766
+ return undefined;
1767
+ }
1768
+ const lastSequence = payload.lastSequence;
1769
+ return typeof lastSequence === "number" &&
1770
+ Number.isSafeInteger(lastSequence) &&
1771
+ lastSequence >= sequence &&
1772
+ lastSequence - sequence < MAX_AGENT_NATIVE_PERSISTED_EVENT_OMISSION_RANGE
1773
+ ? lastSequence
1774
+ : undefined;
1775
+ }
1776
+ function hasContiguousAgentKitEventCoverage(events, runId, lastSequence, firstSequence = 1) {
1777
+ const runEvents = events
1778
+ .filter((event) => event !== null &&
1779
+ typeof event === "object" &&
1780
+ !Array.isArray(event) &&
1781
+ event.runId === runId)
1782
+ .map((event, order) => ({
1783
+ event,
1784
+ order,
1785
+ sequence: getAgentKitEventSequence(event),
1786
+ sequenceEnd: getAgentKitEventSequenceEnd(event),
1787
+ }))
1788
+ .sort((left, right) => {
1789
+ if (left.sequence !== undefined &&
1790
+ right.sequence !== undefined &&
1791
+ left.sequence !== right.sequence) {
1792
+ return left.sequence - right.sequence;
1793
+ }
1794
+ return left.order - right.order;
1795
+ });
1796
+ let nextSequence = firstSequence;
1797
+ for (const event of runEvents) {
1798
+ if (event.sequence !== nextSequence ||
1799
+ event.sequenceEnd === undefined ||
1800
+ event.sequenceEnd < event.sequence) {
1801
+ return false;
1802
+ }
1803
+ nextSequence = event.sequenceEnd + 1;
1804
+ }
1805
+ return nextSequence === lastSequence + 1;
1806
+ }
1671
1807
  function mergeAgentKitFullEventSnapshot(existingEvents, incomingEvents, incomingRuns, committedSnapshots) {
1672
1808
  if (!Array.isArray(incomingEvents))
1673
1809
  return undefined;
@@ -1715,16 +1851,7 @@ function mergeAgentKitFullEventSnapshot(existingEvents, incomingEvents, incoming
1715
1851
  if (committedSequence < 0)
1716
1852
  continue;
1717
1853
  const runEvents = incomingEventsByRun.get(runId) ?? [];
1718
- const incomingSequence = Math.max(incomingRunSequences.get(runId) ?? 0, ...runEvents.map((event) => {
1719
- const sequence = event && typeof event === "object"
1720
- ? event.sequence
1721
- : undefined;
1722
- return typeof sequence === "number" &&
1723
- Number.isSafeInteger(sequence) &&
1724
- sequence >= 0
1725
- ? sequence
1726
- : 0;
1727
- }));
1854
+ const incomingSequence = Math.max(incomingRunSequences.get(runId) ?? 0, ...runEvents.map((event) => getAgentKitEventSequenceEnd(event) ?? 0));
1728
1855
  if (runEvents.length === 0 || incomingSequence <= committedSequence) {
1729
1856
  protectedRunIds.add(runId);
1730
1857
  }
@@ -1807,11 +1934,24 @@ function mergeAgentKitEventRunSnapshots(input) {
1807
1934
  const hasCommittedSnapshot = (commits[runId]?.length ?? 0) > 0;
1808
1935
  const committedSequence = Math.max(0, ...(commits[runId] ?? []).map((entry) => entry.lastSequence));
1809
1936
  const knownSequence = Math.max(storedWatermark ?? 0, storedRunSequence ?? 0, committedSequence);
1937
+ if (!input.batches.has(runId) && lastSequence > knownSequence) {
1938
+ const incomingEvents = Array.isArray(input.incomingEvents)
1939
+ ? input.incomingEvents
1940
+ : [];
1941
+ const completeFromStart = hasContiguousAgentKitEventCoverage(incomingEvents, runId, lastSequence);
1942
+ const nextChunk = hasContiguousAgentKitEventCoverage(incomingEvents, runId, lastSequence, knownSequence + 1);
1943
+ if (!completeFromStart && !nextChunk) {
1944
+ throw new TypeError("Agent chat event snapshot did not contain a contiguous sequence range.");
1945
+ }
1946
+ }
1810
1947
  const hasKnownSequence = storedWatermark !== undefined ||
1811
1948
  storedRunSequence !== undefined ||
1812
1949
  hasCommittedSnapshot;
1813
1950
  const hasStoredEvents = previousEvents.some((event) => event?.runId === runId);
1814
1951
  if ((hasKnownSequence && lastSequence < knownSequence) ||
1952
+ (hasKnownSequence &&
1953
+ lastSequence === knownSequence &&
1954
+ !input.incomingReplacements.has(runId)) ||
1815
1955
  (hasCommittedSnapshot && lastSequence <= committedSequence)) {
1816
1956
  staleRunIds.add(runId);
1817
1957
  continue;
@@ -1896,8 +2036,7 @@ function mergeAgentKitEventRunSnapshots(input) {
1896
2036
  const matchingCurrent = current?.lastSequence === batch.lastSequence &&
1897
2037
  current.expectedEventCount === batch.expectedEventCount;
1898
2038
  const batchEvents = incomingEvents.filter((event) => event?.runId === runId);
1899
- const stagedEvents = mergeAgentKitEvents(matchingCurrent ? current.events : [], batchEvents) ??
1900
- [];
2039
+ const stagedEvents = mergeAgentKitEventSnapshotBatch(matchingCurrent ? current.events : [], batchEvents);
1901
2040
  if (stagedEvents.length > batch.expectedEventCount) {
1902
2041
  throw new TypeError("Agent chat event snapshot exceeded its event count.");
1903
2042
  }
@@ -1905,6 +2044,18 @@ function mergeAgentKitEventRunSnapshots(input) {
1905
2044
  throw new TypeError("Agent chat event snapshot ended before all events arrived.");
1906
2045
  }
1907
2046
  if (stagedEvents.length === batch.expectedEventCount) {
2047
+ const lastEventSequence = stagedEvents.reduce((maximum, event) => {
2048
+ const sequence = getAgentKitEventSequenceEnd(event);
2049
+ return sequence !== undefined && sequence >= 0
2050
+ ? Math.max(maximum, sequence)
2051
+ : maximum;
2052
+ }, 0);
2053
+ if (batch.lastSequence !== lastEventSequence) {
2054
+ throw new TypeError(`Agent chat event snapshot cursor ${batch.lastSequence} did not match its last event sequence ${lastEventSequence}.`);
2055
+ }
2056
+ if (!hasContiguousAgentKitEventCoverage(stagedEvents, runId, batch.lastSequence)) {
2057
+ throw new TypeError("Agent chat event snapshot did not include every sequence through its cursor.");
2058
+ }
1908
2059
  events = mergeAgentKitEvents(events, stagedEvents, new Set([runId]));
1909
2060
  watermarks.set(runId, Math.max(knownSequence, batch.lastSequence));
1910
2061
  commits[runId] = [
@@ -2051,7 +2202,7 @@ function mergeAgentKitAnnotations(existing, incoming, replaceMessageIds, removal
2051
2202
  }
2052
2203
  return merged;
2053
2204
  }
2054
- function mergeAgentKitHistory(existing, incoming, promptRunIds, onAnnotationConflict) {
2205
+ function mergeAgentKitHistory(existing, incoming, promptRunIds, onAnnotationConflict, trustedRunSequenceResetIds = new Set()) {
2055
2206
  const incomingIsRecord = incoming !== null &&
2056
2207
  typeof incoming === "object" &&
2057
2208
  !Array.isArray(incoming);
@@ -2062,10 +2213,27 @@ function mergeAgentKitHistory(existing, incoming, promptRunIds, onAnnotationConf
2062
2213
  typeof existing === "object" &&
2063
2214
  !Array.isArray(existing);
2064
2215
  const snapshotDelta = next._snapshotDelta === true;
2065
- const incomingEventRunIds = new Set((Array.isArray(next.events) ? next.events : []).flatMap((event) => typeof event?.runId === "string" ? [event.runId] : []));
2066
- const incomingRunStarts = new Set((Array.isArray(next.events) ? next.events : []).flatMap((event) => typeof event?.runId === "string" && event.sequence === 1
2216
+ const incomingEvents = Array.isArray(next.events) ? next.events : [];
2217
+ for (const event of incomingEvents) {
2218
+ if (event?.type === "x-agent-native.persisted-event-omission-range" &&
2219
+ getAgentKitEventSequenceEnd(event) === undefined) {
2220
+ throw new TypeError(`Agent chat persisted omission ranges must cover at most ${MAX_AGENT_NATIVE_PERSISTED_EVENT_OMISSION_RANGE} event sequences.`);
2221
+ }
2222
+ }
2223
+ const incomingEventRunIds = new Set(incomingEvents.flatMap((event) => typeof event?.runId === "string" ? [event.runId] : []));
2224
+ const incomingRunStarts = new Set(incomingEvents.flatMap((event) => typeof event?.runId === "string" && event.sequence === 1
2067
2225
  ? [event.runId]
2068
2226
  : []));
2227
+ const incomingEventSequences = new Map();
2228
+ for (const event of incomingEvents) {
2229
+ if (typeof event?.runId !== "string") {
2230
+ continue;
2231
+ }
2232
+ const sequenceEnd = getAgentKitEventSequenceEnd(event);
2233
+ if (sequenceEnd === undefined || sequenceEnd < 0)
2234
+ continue;
2235
+ incomingEventSequences.set(event.runId, Math.max(incomingEventSequences.get(event.runId) ?? 0, sequenceEnd));
2236
+ }
2069
2237
  const eventRunSnapshotBatches = new Map();
2070
2238
  for (const batch of Array.isArray(next.eventRunSnapshotBatches)
2071
2239
  ? next.eventRunSnapshotBatches
@@ -2091,27 +2259,25 @@ function mergeAgentKitHistory(existing, incoming, promptRunIds, onAnnotationConf
2091
2259
  }
2092
2260
  }
2093
2261
  const eventRunSnapshotWatermarks = new Map();
2094
- for (const watermark of Array.isArray(next.eventRunSnapshotWatermarks)
2095
- ? next.eventRunSnapshotWatermarks
2096
- : []) {
2097
- if (typeof watermark?.runId === "string" &&
2098
- typeof watermark.lastSequence === "number" &&
2099
- Number.isSafeInteger(watermark.lastSequence) &&
2100
- watermark.lastSequence >= 0 &&
2101
- incomingEventRunIds.has(watermark.runId) &&
2102
- !eventRunSnapshotBatches.has(watermark.runId)) {
2103
- eventRunSnapshotWatermarks.set(watermark.runId, Math.max(eventRunSnapshotWatermarks.get(watermark.runId) ?? 0, watermark.lastSequence));
2262
+ for (const [runId, lastSequence] of incomingEventSequences) {
2263
+ if (incomingEventRunIds.has(runId) && !eventRunSnapshotBatches.has(runId)) {
2264
+ eventRunSnapshotWatermarks.set(runId, lastSequence);
2104
2265
  }
2105
2266
  }
2106
2267
  const eventRunReplacements = new Map();
2107
2268
  for (const replacement of Array.isArray(next.eventRunReplacements)
2108
2269
  ? next.eventRunReplacements
2109
2270
  : []) {
2271
+ const eventSequence = typeof replacement?.runId === "string"
2272
+ ? incomingEventSequences.get(replacement.runId)
2273
+ : undefined;
2110
2274
  if (typeof replacement?.runId === "string" &&
2275
+ incomingRunStarts.has(replacement.runId) &&
2111
2276
  typeof replacement.lastSequence === "number" &&
2112
2277
  Number.isSafeInteger(replacement.lastSequence) &&
2113
2278
  replacement.lastSequence >= 1 &&
2114
- incomingRunStarts.has(replacement.runId) &&
2279
+ eventSequence === replacement.lastSequence &&
2280
+ hasContiguousAgentKitEventCoverage(incomingEvents, replacement.runId, replacement.lastSequence) &&
2115
2281
  !eventRunSnapshotBatches.has(replacement.runId)) {
2116
2282
  eventRunReplacements.set(replacement.runId, Math.max(eventRunReplacements.get(replacement.runId) ?? 0, replacement.lastSequence));
2117
2283
  }
@@ -2214,20 +2380,29 @@ function mergeAgentKitHistory(existing, incoming, promptRunIds, onAnnotationConf
2214
2380
  runs.set(run.id, run);
2215
2381
  continue;
2216
2382
  }
2217
- const richer = newer.lastSequence >= run.lastSequence ? newer : run;
2218
- const statusRun = run.lastSequence > newer.lastSequence
2219
- ? run
2220
- : newer.lastSequence > run.lastSequence
2383
+ const trustedReset = trustedRunSequenceResetIds.has(run.id) && newer.lastSequence === 0;
2384
+ const richer = trustedReset
2385
+ ? newer
2386
+ : newer.lastSequence >= run.lastSequence
2221
2387
  ? newer
2222
- : isTerminalRunStatus(run.status)
2223
- ? run
2224
- : newer;
2388
+ : run;
2389
+ const statusRun = trustedReset
2390
+ ? newer
2391
+ : run.lastSequence > newer.lastSequence
2392
+ ? run
2393
+ : newer.lastSequence > run.lastSequence
2394
+ ? newer
2395
+ : isTerminalRunStatus(run.status)
2396
+ ? run
2397
+ : newer;
2225
2398
  runs.set(run.id, {
2226
2399
  ...run,
2227
2400
  ...newer,
2228
2401
  ...richer,
2229
2402
  metadata: { ...run.metadata, ...newer.metadata, ...richer.metadata },
2230
- lastSequence: Math.max(newer.lastSequence, run.lastSequence),
2403
+ lastSequence: trustedReset
2404
+ ? newer.lastSequence
2405
+ : Math.max(newer.lastSequence, run.lastSequence),
2231
2406
  status: statusRun.status,
2232
2407
  });
2233
2408
  }
@@ -2437,7 +2612,7 @@ export function mergeThreadDataForClientSave(existingRepo, incomingRepo, options
2437
2612
  }
2438
2613
  const promptRunIds = submittedPromptRunIds(existingNormalized?.messages, incomingNormalized?.messages);
2439
2614
  if (merged.agentKit !== undefined) {
2440
- merged.agentKit = mergeAgentKitHistory(existingNormalized?.agentKit, merged.agentKit, promptRunIds, options.onAnnotationConflict);
2615
+ merged.agentKit = mergeAgentKitHistory(existingNormalized?.agentKit, merged.agentKit, promptRunIds, options.onAnnotationConflict, options.trustedRunSequenceResetIds);
2441
2616
  }
2442
2617
  const existingMessages = Array.isArray(existingNormalized?.messages)
2443
2618
  ? existingNormalized.messages
@@ -1,3 +1,5 @@
1
+ export declare function failedRunIdsWithError(runs: unknown): Set<string>;
2
+ export declare function isRenderedRunNotStartedNotice(value: unknown, failedRunIds: ReadonlySet<string>): boolean;
1
3
  export declare function threadMessageRecord(value: unknown): Record<string, unknown> | undefined;
2
4
  /** AgentKit messages that represent durable root assistant messages. */
3
5
  export declare function projectRootAssistantMessages(input: {
@@ -10,6 +12,7 @@ export declare function projectRootAssistantMessages(input: {
10
12
  representedRootMessageIds: Set<string>;
11
13
  snapshotMessageIdsByRootMessageId: Map<string, string>;
12
14
  snapshotRunIdsByMessageId: Map<string, string>;
15
+ supersededSnapshotMessageIds: Set<string>;
13
16
  };
14
17
  /** Root assistant IDs represented by the AgentKit snapshot. */
15
18
  export declare function representedRootAssistantMessageIds(input: {
@@ -1,9 +1,28 @@
1
+ import { RUN_NOT_STARTED_METADATA_KEY } from "../shared/agent-chat-run-not-started.js";
1
2
  const REPOSITORY_PART_MEDIA_TYPE = "application/x-agent-native-repository-part";
2
3
  function record(value) {
3
4
  return value && typeof value === "object" && !Array.isArray(value)
4
5
  ? value
5
6
  : undefined;
6
7
  }
8
+ export function failedRunIdsWithError(runs) {
9
+ return new Set((Array.isArray(runs) ? runs : []).flatMap((value) => {
10
+ const run = record(value);
11
+ return run?.status === "failed" &&
12
+ record(run.error) &&
13
+ typeof run.id === "string"
14
+ ? [run.id]
15
+ : [];
16
+ }));
17
+ }
18
+ export function isRenderedRunNotStartedNotice(value, failedRunIds) {
19
+ const entry = message(value);
20
+ const metadata = record(entry?.metadata);
21
+ return (entry?.role === "assistant" &&
22
+ record(metadata?.custom)?.[RUN_NOT_STARTED_METADATA_KEY] === true &&
23
+ typeof metadata?.runId === "string" &&
24
+ failedRunIds.has(metadata.runId));
25
+ }
7
26
  function message(value) {
8
27
  const outer = record(value);
9
28
  return record(outer?.message) ?? outer;
@@ -37,6 +56,15 @@ function explicitRunId(value) {
37
56
  const runId = record(value.metadata)?.runId;
38
57
  return typeof runId === "string" ? runId : undefined;
39
58
  }
59
+ function isTerminalAssistant(value) {
60
+ const status = value.status;
61
+ const type = record(status)?.type;
62
+ return (status === "complete" ||
63
+ status === "error" ||
64
+ type === "complete" ||
65
+ type === "error" ||
66
+ type === "incomplete");
67
+ }
40
68
  function foldedRunIds(value) {
41
69
  const custom = record(record(value.metadata)?.custom);
42
70
  const folded = Array.isArray(custom?.foldedRunIds)
@@ -115,6 +143,26 @@ function reasoning(value) {
115
143
  function isPrefixCompatible(left, right) {
116
144
  return left.startsWith(right) || right.startsWith(left);
117
145
  }
146
+ function isRepeatedAnswerSnapshot(answer, candidateText) {
147
+ if (!answer || !candidateText.startsWith(answer))
148
+ return false;
149
+ let offset = answer.length;
150
+ let repeatedAnswers = 0;
151
+ while (offset < candidateText.length) {
152
+ while (offset < candidateText.length &&
153
+ /\s/u.test(candidateText[offset])) {
154
+ offset += 1;
155
+ }
156
+ if (offset === candidateText.length)
157
+ return repeatedAnswers > 0;
158
+ if (!candidateText.startsWith(answer, offset)) {
159
+ return false;
160
+ }
161
+ offset += answer.length;
162
+ repeatedAnswers += 1;
163
+ }
164
+ return repeatedAnswers > 0;
165
+ }
118
166
  function rootToolCalls(value) {
119
167
  return parts(value).flatMap((part) => {
120
168
  if (part.type !== "data" || part.mediaType !== REPOSITORY_PART_MEDIA_TYPE) {
@@ -124,6 +172,39 @@ function rootToolCalls(value) {
124
172
  return data?.type === "tool-call" ? [data] : [];
125
173
  });
126
174
  }
175
+ function sameToolCallIdentity(left, right) {
176
+ const id = (call) => typeof call.toolCallId === "string" ? call.toolCallId : call.id;
177
+ const name = (call) => typeof call.toolName === "string" ? call.toolName : call.name;
178
+ const args = (call) => Object.prototype.hasOwnProperty.call(call, "args") ? call.args : call.input;
179
+ const hasArgs = (call) => Object.prototype.hasOwnProperty.call(call, "args") ||
180
+ Object.prototype.hasOwnProperty.call(call, "input");
181
+ return (typeof id(left) === "string" &&
182
+ id(left) === id(right) &&
183
+ typeof name(left) === "string" &&
184
+ name(left) === name(right) &&
185
+ hasArgs(left) &&
186
+ hasArgs(right) &&
187
+ sameValue(args(left), args(right)));
188
+ }
189
+ function snapshotAdditionalContentIsRepresented(root, candidate) {
190
+ const candidateReasoning = reasoning(candidate);
191
+ if (candidateReasoning && candidateReasoning !== reasoning(root)) {
192
+ return false;
193
+ }
194
+ const rootCalls = rootToolCalls(root);
195
+ return rootToolCalls(candidate).every((candidateCall) => rootCalls.some((rootCall) => sameToolCallIdentity(rootCall, candidateCall)));
196
+ }
197
+ function snapshotRepresentsRootContent(root, candidate, toolMessageIds) {
198
+ const rootCalls = rootToolCalls(root);
199
+ const candidateCalls = rootToolCalls(candidate);
200
+ if (rootCalls.every((rootCall) => candidateCalls.some((candidateCall) => sameToolCallIdentity(rootCall, candidateCall)))) {
201
+ return true;
202
+ }
203
+ return (rootCalls.length > 0 &&
204
+ typeof candidate.id === "string" &&
205
+ toolMessageIds?.size === 1 &&
206
+ toolMessageIds.has(candidate.id));
207
+ }
127
208
  function sameValue(left, right) {
128
209
  if (Object.is(left, right))
129
210
  return true;
@@ -294,17 +375,68 @@ export function projectRootAssistantMessages(input) {
294
375
  }
295
376
  const represented = new Set();
296
377
  const directMatches = new Map();
378
+ const rootAssistantCountsByRun = new Map();
379
+ for (const root of rootMessages) {
380
+ const runId = assistantRunId(root, runIdsByMessageId);
381
+ if (!runId)
382
+ continue;
383
+ rootAssistantCountsByRun.set(runId, (rootAssistantCountsByRun.get(runId) ?? 0) + 1);
384
+ }
385
+ const supersededSnapshotMessageIds = new Set();
297
386
  for (const root of rootMessages) {
298
387
  if (root.role !== "assistant" || typeof root.id !== "string")
299
388
  continue;
300
- const runId = explicitRunId(root);
389
+ const runId = assistantRunId(root, runIdsByMessageId);
301
390
  if (!runId)
302
391
  continue;
303
392
  const toolMessageIds = representedToolCallMessageIds(root, input.toolCalls);
304
393
  const sameRunCandidates = (snapshotMessagesByRunId.get(runId) ?? []).filter((candidate) => candidate.id !== root.id);
305
394
  const sameRunMatches = sameRunCandidates.filter((candidate) => contentMatches(root, candidate, toolMessageIds));
306
395
  const rootText = text(root);
307
- const matchingCandidate = sameRunMatches[0];
396
+ const exactTextMatches = sameRunMatches.filter((candidate) => rootText !== undefined && text(candidate) === rootText);
397
+ const duplicateCandidates = rootText && isTerminalAssistant(root)
398
+ ? sameRunCandidates.filter((candidate) => {
399
+ const candidateText = text(candidate);
400
+ return (candidateText === rootText ||
401
+ Boolean(candidateText &&
402
+ isRepeatedAnswerSnapshot(rootText, candidateText)));
403
+ })
404
+ : [];
405
+ if (rootText &&
406
+ isTerminalAssistant(root) &&
407
+ rootAssistantCountsByRun.get(runId) === 1 &&
408
+ duplicateCandidates.length > 0) {
409
+ const matchingCandidate = exactTextMatches.find((candidate) => snapshotRepresentsRootContent(root, candidate, toolMessageIds) &&
410
+ snapshotAdditionalContentIsRepresented(root, candidate));
411
+ if (matchingCandidate && typeof matchingCandidate.id === "string") {
412
+ represented.add(root.id);
413
+ directMatches.set(root.id, matchingCandidate.id);
414
+ }
415
+ for (const candidate of duplicateCandidates) {
416
+ if (matchingCandidate && candidate.id === matchingCandidate.id) {
417
+ continue;
418
+ }
419
+ const candidateText = text(candidate);
420
+ if (candidateText &&
421
+ snapshotAdditionalContentIsRepresented(root, candidate) &&
422
+ typeof candidate.id === "string") {
423
+ supersededSnapshotMessageIds.add(candidate.id);
424
+ }
425
+ }
426
+ continue;
427
+ }
428
+ const matchingCandidates = rootText && isTerminalAssistant(root)
429
+ ? sameRunMatches.filter((candidate) => {
430
+ const candidateText = text(candidate);
431
+ return (!candidateText ||
432
+ !isRepeatedAnswerSnapshot(rootText, candidateText));
433
+ })
434
+ : sameRunMatches;
435
+ const fallbackExactMatches = matchingCandidates.filter((candidate) => rootText !== undefined && text(candidate) === rootText);
436
+ const candidateMatches = fallbackExactMatches.length > 0
437
+ ? fallbackExactMatches
438
+ : matchingCandidates;
439
+ const matchingCandidate = candidateMatches[0];
308
440
  const exactTextMatch = matchingCandidate !== undefined &&
309
441
  rootText !== undefined &&
310
442
  text(matchingCandidate) === rootText;
@@ -314,7 +446,7 @@ export function projectRootAssistantMessages(input) {
314
446
  (snapshotRunIdsByMessageId.get(matchingCandidate.id) === runId ||
315
447
  (runIdsByMessageId.get(matchingCandidate.id)?.size === 1 &&
316
448
  messageIdsByRun.get(runId)?.size === 1))));
317
- if (sameRunMatches.length === 1 &&
449
+ if (candidateMatches.length === 1 &&
318
450
  (exactTextMatch ||
319
451
  (sameRunCandidates.length === 1 && clientCanMatchPartial))) {
320
452
  represented.add(root.id);
@@ -344,6 +476,7 @@ export function projectRootAssistantMessages(input) {
344
476
  representedRootMessageIds: represented,
345
477
  snapshotMessageIdsByRootMessageId,
346
478
  snapshotRunIdsByMessageId,
479
+ supersededSnapshotMessageIds,
347
480
  };
348
481
  }
349
482
  /** Root assistant IDs represented by the AgentKit snapshot. */
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
139
139
  duplicate?: undefined;
140
140
  eventId?: undefined;
141
141
  } | {
142
- error?: undefined;
143
142
  accepted: boolean;
144
143
  duplicate: boolean;
145
144
  eventId: string;
145
+ error?: undefined;
146
146
  }>;
@@ -189,6 +189,7 @@ export interface UpdateThreadDataOptions {
189
189
  preserveExistingTopLevelKeys?: boolean;
190
190
  preserveCurrentMetadata?: boolean;
191
191
  preserveCurrentTitleAndPreview?: boolean;
192
+ trustedRunSequenceResetIds?: ReadonlySet<string>;
192
193
  onAnnotationConflict?: (conflict: ThreadAnnotationSnapshotConflict) => void;
193
194
  transformThreadData?: (currentThreadData: string) => string | {
194
195
  threadData: string;
@@ -1,7 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import { parseQueueMessageInput, } from "@agent-native/agentkit/protocol";
3
3
  import { extractThreadMeta, mergeThreadDataForClientSave, normalizeThreadRepository, normalizeThreadTitle, } from "../agent/thread-data-builder.js";
4
- import { representedRootAssistantMessageIds, threadMessageRecord, } from "../agent/thread-message-projection.js";
4
+ import { failedRunIdsWithError, isRenderedRunNotStartedNotice, projectRootAssistantMessages, threadMessageRecord, } from "../agent/thread-message-projection.js";
5
5
  import { getDbExec } from "../db/client.js";
6
6
  import { createGetDb } from "../db/create-get-db.js";
7
7
  import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../db/ddl-guard.js";
@@ -293,17 +293,20 @@ function countThreadMessages(value, fallback) {
293
293
  if (repoMessageCount === undefined && agentKitMessageCount === undefined) {
294
294
  return fallback;
295
295
  }
296
- const rootMessages = Array.isArray(repo.messages) ? repo.messages : [];
296
+ const failedRunIds = failedRunIdsWithError(repo.agentKit?.runs);
297
+ const rootMessages = (Array.isArray(repo.messages) ? repo.messages : []).filter((entry) => !isRenderedRunNotStartedNotice(entry, failedRunIds));
297
298
  const agentKitMessages = Array.isArray(repo.agentKit?.messages)
298
299
  ? repo.agentKit.messages
299
300
  : [];
300
- const mirroredRootIds = representedRootAssistantMessageIds({
301
+ const assistantProjection = projectRootAssistantMessages({
301
302
  rootMessages,
302
303
  snapshotMessages: agentKitMessages,
303
304
  events: repo.agentKit?.events,
304
305
  runs: repo.agentKit?.runs,
305
306
  toolCalls: repo.agentKit?.toolCalls,
306
307
  });
308
+ const mirroredRootIds = assistantProjection.representedRootMessageIds;
309
+ const supersededSnapshotMessageIds = assistantProjection.supersededSnapshotMessageIds;
307
310
  const agentKitUserMessageId = (entry) => {
308
311
  const message = threadMessageRecord(entry);
309
312
  if (message?.role !== "user")
@@ -362,8 +365,14 @@ function countThreadMessages(value, fallback) {
362
365
  }
363
366
  countMessage(entry);
364
367
  }
365
- for (const entry of agentKitMessages)
368
+ for (const entry of agentKitMessages) {
369
+ const message = threadMessageRecord(entry);
370
+ if (typeof message?.id === "string" &&
371
+ supersededSnapshotMessageIds.has(message.id)) {
372
+ continue;
373
+ }
366
374
  countMessage(entry);
375
+ }
367
376
  return messageIds.size + unkeyedMessages;
368
377
  }
369
378
  function forkThreadData(threadData, forkId, fromMessageId) {
@@ -1124,6 +1133,7 @@ export async function updateThreadData(id, threadData, title, preview, messageCo
1124
1133
  const merged = mergeThreadDataForClientSave(safeCurrentThreadData, parseThreadData(incomingThreadData), {
1125
1134
  preserveExistingQueuedMessages: options.preserveExistingQueuedMessages ?? true,
1126
1135
  preserveExistingTopLevelKeys: options.preserveExistingTopLevelKeys ?? true,
1136
+ trustedRunSequenceResetIds: options.trustedRunSequenceResetIds,
1127
1137
  onAnnotationConflict: (conflict) => annotationConflicts.push(conflict),
1128
1138
  });
1129
1139
  // Client snapshots can predate their upload URL, so inline bytes become a