agents 0.15.0 → 0.16.0

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 (53) hide show
  1. package/dist/{agent-tool-types-VPsjVYL0.d.ts → agent-tool-types-NofdbL9X.d.ts} +57 -4
  2. package/dist/agent-tool-types.d.ts +1 -1
  3. package/dist/{agent-tools-BGpgfpJT.d.ts → agent-tools-DLquv-dp.d.ts} +2 -2
  4. package/dist/agent-tools.d.ts +1 -1
  5. package/dist/browser/ai.d.ts +126 -7
  6. package/dist/browser/ai.js +73 -29
  7. package/dist/browser/ai.js.map +1 -1
  8. package/dist/browser/index.d.ts +81 -69
  9. package/dist/browser/index.js +3 -2
  10. package/dist/browser/tanstack-ai.d.ts +13 -7
  11. package/dist/browser/tanstack-ai.js +18 -19
  12. package/dist/browser/tanstack-ai.js.map +1 -1
  13. package/dist/chat/index.d.ts +111 -5
  14. package/dist/chat/index.js +207 -35
  15. package/dist/chat/index.js.map +1 -1
  16. package/dist/chat-sdk/index.d.ts +1 -1
  17. package/dist/{classPrivateFieldGet2-Beqsfu2Z.js → classPrivateFieldGet2-CZ7QjTXN.js} +5 -5
  18. package/dist/{classPrivateMethodInitSpec-B5ko1s2R.js → classPrivateMethodInitSpec-D-0__zd9.js} +2 -2
  19. package/dist/client.d.ts +19 -2
  20. package/dist/client.js +31 -11
  21. package/dist/client.js.map +1 -1
  22. package/dist/{compaction-helpers-BEUILPss.d.ts → compaction-helpers-DVcu5lPN.d.ts} +91 -12
  23. package/dist/connector-D6yYzYHg.js +1080 -0
  24. package/dist/connector-D6yYzYHg.js.map +1 -0
  25. package/dist/connector-DXursxV5.d.ts +340 -0
  26. package/dist/experimental/memory/session/index.d.ts +75 -12
  27. package/dist/experimental/memory/session/index.js +226 -21
  28. package/dist/experimental/memory/session/index.js.map +1 -1
  29. package/dist/experimental/memory/utils/index.d.ts +2 -2
  30. package/dist/{index-CPe1OtI0.d.ts → index-B7IbEeze.d.ts} +32 -1
  31. package/dist/index.d.ts +8 -2
  32. package/dist/index.js +116 -45
  33. package/dist/index.js.map +1 -1
  34. package/dist/mcp/client.d.ts +1 -1
  35. package/dist/mcp/index.d.ts +1 -1
  36. package/dist/mcp/index.js +1 -1
  37. package/dist/mcp/index.js.map +1 -1
  38. package/dist/observability/index.d.ts +1 -1
  39. package/dist/react.d.ts +12 -1
  40. package/dist/react.js +101 -30
  41. package/dist/react.js.map +1 -1
  42. package/dist/{retries-CF_HKSlJ.d.ts → retries-CwlpAGet.d.ts} +35 -5
  43. package/dist/retries.d.ts +9 -5
  44. package/dist/retries.js +87 -1
  45. package/dist/retries.js.map +1 -1
  46. package/dist/serializable.d.ts +1 -1
  47. package/dist/skills/index.js +2 -2
  48. package/dist/sub-routing.d.ts +1 -1
  49. package/dist/workflows.d.ts +1 -1
  50. package/package.json +10 -10
  51. package/dist/shared-4CAYLCTO.d.ts +0 -34
  52. package/dist/shared-wyII629d.js +0 -432
  53. package/dist/shared-wyII629d.js.map +0 -1
@@ -1,22 +1,24 @@
1
1
  import {
2
- A as CompactContext,
2
+ A as StoredCompaction,
3
3
  C as isSearchProvider,
4
- D as SessionProvider,
5
- E as SearchResult,
6
- F as SessionTokenCounter,
7
- I as SessionTokenCounterInput,
8
- M as SessionMessage,
9
- N as SessionMessagePart,
10
- O as StoredCompaction,
11
- P as SessionOptions,
4
+ D as RecentHistoryResult,
5
+ E as HistoryRowStat,
6
+ F as SessionMessagePart,
7
+ I as SessionOptions,
8
+ L as SessionTokenCounter,
9
+ M as CompactContext,
10
+ N as CompactionErrorHandler,
11
+ O as SearchResult,
12
+ P as SessionMessage,
13
+ R as SessionTokenCounterInput,
12
14
  S as SearchProvider,
13
15
  T as SqlProvider,
14
16
  _ as isWritableProvider,
15
17
  b as isSkillProvider,
16
18
  g as WritableContextProvider,
17
19
  h as ContextProvider,
18
- j as CompactionErrorHandler,
19
- k as CompactAfterOptions,
20
+ j as CompactAfterOptions,
21
+ k as SessionProvider,
20
22
  m as ContextConfig,
21
23
  p as ContextBlock,
22
24
  r as CompactResult,
@@ -24,7 +26,7 @@ import {
24
26
  w as AgentSessionProvider,
25
27
  x as AgentSearchProvider,
26
28
  y as SkillProvider
27
- } from "../../../compaction-helpers-BEUILPss.js";
29
+ } from "../../../compaction-helpers-DVcu5lPN.js";
28
30
  import { ToolSet } from "ai";
29
31
 
30
32
  //#region src/experimental/memory/session/session.d.ts
@@ -151,8 +153,31 @@ declare class Session {
151
153
  * for load_context tool results that haven't been unloaded.
152
154
  * Runs once per init to survive hibernation / eviction, including for
153
155
  * async SessionProviders (e.g. Postgres) where we must `await` history.
156
+ *
157
+ * Skipped entirely when no skill-capable provider is configured —
158
+ * `load_context` results can only exist when a skill block was registered,
159
+ * and the scan would otherwise read the whole transcript on every wake,
160
+ * bypassing byte-budgeted hydration (#1710). A skill block added later via
161
+ * `addContext()` triggers the scan at that point instead.
154
162
  */
155
163
  private _restoreLoadedSkills;
164
+ private _skillScanRan;
165
+ /**
166
+ * Scan stored history for load/unload_context tool results and restore
167
+ * the loaded-skill tracking set.
168
+ *
169
+ * Memory-bounded when the provider supports `getHistoryRowStats`: rows
170
+ * are enumerated without content, then only assistant rows are fetched
171
+ * and scanned ONE AT A TIME — peak memory is a single message instead of
172
+ * the whole transcript. Falls back to a full `getHistory()` read for
173
+ * providers without row stats (e.g. Postgres).
174
+ *
175
+ * Note: the bounded path scans raw path rows, so `load_context` results
176
+ * inside compacted ranges are still seen (the full-read path hides them
177
+ * behind compaction overlays). That superset is intentional — the stored
178
+ * tool result still exists and can be reclaimed by `unloadSkill`.
179
+ */
180
+ private _scanHistoryForLoadedSkills;
156
181
  /**
157
182
  * Reclaim context-window tokens consumed by a previously loaded skill.
158
183
  *
@@ -170,6 +195,30 @@ declare class Session {
170
195
  */
171
196
  private _reclaimLoadedSkill;
172
197
  getHistory(leafId?: string | null): Promise<SessionMessage[]>;
198
+ private _warnedNoRecentHistorySupport;
199
+ /**
200
+ * Byte-budgeted read of the most recent messages on the active branch
201
+ * path (always at least the leaf message, and at least
202
+ * `minRecentMessages` when the path is long enough). Lets hosts hydrate
203
+ * a bounded window instead of the full transcript so wake-time memory
204
+ * scales with the budget rather than total session history (#1710).
205
+ *
206
+ * Falls back to a full (untruncated) read when the provider doesn't
207
+ * implement `getRecentHistory`. The fallback reports honest metadata
208
+ * (`truncated: false` and the real serialized size) and warns once so a
209
+ * host relying on the budget knows it is not being enforced.
210
+ */
211
+ getRecentHistory(
212
+ maxContentBytes: number,
213
+ minRecentMessages?: number
214
+ ): Promise<RecentHistoryResult>;
215
+ /**
216
+ * Per-row stored sizes for the active branch path (root → leaf) WITHOUT
217
+ * loading message content, or `null` when the provider doesn't support it.
218
+ * Lets hosts find oversized rows (e.g. inline base64 media) and process
219
+ * them one at a time with bounded memory.
220
+ */
221
+ getHistoryRowStats(): Promise<HistoryRowStat[] | null>;
173
222
  getMessage(id: string): Promise<SessionMessage | null>;
174
223
  getLatestLeaf(): Promise<SessionMessage | null>;
175
224
  getBranches(messageId: string): Promise<SessionMessage[]>;
@@ -186,6 +235,18 @@ declare class Session {
186
235
  ): Promise<void>;
187
236
  private _appendMessage;
188
237
  updateMessage(message: SessionMessage): Promise<void>;
238
+ /**
239
+ * @internal
240
+ * Rewrite a stored message WITHOUT the public-write side effects: no
241
+ * token-estimate status broadcast (which reads the FULL history) and no
242
+ * auto-compaction check. For framework maintenance passes that rewrite
243
+ * many rows with bounded memory — e.g. media eviction (#1710) — where the
244
+ * per-row full-history estimate would reintroduce the memory pressure the
245
+ * pass exists to remove. The message-change listener still fires so a
246
+ * cache-owning host stays coherent. Application code should use
247
+ * `updateMessage`.
248
+ */
249
+ internal_rewriteMessage(message: SessionMessage): Promise<void>;
189
250
  deleteMessages(messageIds: string[]): Promise<void>;
190
251
  clearMessages(): Promise<void>;
191
252
  addCompaction(
@@ -576,6 +637,7 @@ export {
576
637
  type ContextBlock,
577
638
  type ContextConfig,
578
639
  type ContextProvider,
640
+ type HistoryRowStat,
579
641
  type PgClientLike,
580
642
  type PostgresClient,
581
643
  type PostgresConnection,
@@ -583,6 +645,7 @@ export {
583
645
  PostgresSearchProvider,
584
646
  PostgresSessionProvider,
585
647
  R2SkillProvider,
648
+ type RecentHistoryResult,
586
649
  type SearchProvider,
587
650
  type SearchResult,
588
651
  Session,
@@ -482,6 +482,14 @@ var ContextBlocks = class {
482
482
  return Array.from(this.blocks.values()).some((b) => b.isSkill);
483
483
  }
484
484
  /**
485
+ * Check whether any CONFIGURED provider is skill-capable, without
486
+ * requiring `load()` to have run. Used by Session to decide whether the
487
+ * init-time loaded-skill restore scan is needed at all.
488
+ */
489
+ hasSkillCapableConfigs() {
490
+ return this.configs.some((c) => c.provider !== void 0 && isSkillProvider(c.provider));
491
+ }
492
+ /**
485
493
  * Get skill block labels.
486
494
  */
487
495
  getSkillLabels() {
@@ -708,6 +716,22 @@ var ContextBlocks = class {
708
716
  };
709
717
  //#endregion
710
718
  //#region src/experimental/memory/session/providers/agent.ts
719
+ /**
720
+ * Bounds for each content-hydration query on a history path.
721
+ *
722
+ * Message rows can be up to ~1.8MB each (see ROW_MAX_BYTES in agents/chat),
723
+ * so content is fetched in bounded batches rather than one statement to keep
724
+ * any per-statement buffering in the SQLite layer small. In workerd the
725
+ * SQLite allocator shares the isolate's memory budget with the JS heap —
726
+ * oversized transient result sets surface as SQLITE_NOMEM (#1710).
727
+ *
728
+ * Chunks are bounded by BOTH row count and cumulative stored bytes (sizes
729
+ * come from the path row stats, so no content is read to compute them).
730
+ * Without the byte bound, 50 near-cap rows could still materialize ~90MB
731
+ * in a single statement.
732
+ */
733
+ const HISTORY_CONTENT_CHUNK_SIZE = 50;
734
+ const HISTORY_CONTENT_CHUNK_BYTES = 4 * 1024 * 1024;
711
735
  var AgentSessionProvider = class {
712
736
  /**
713
737
  * @param agent - Agent or any object with a `sql` tagged template method
@@ -772,29 +796,47 @@ var AgentSessionProvider = class {
772
796
  }
773
797
  getHistory(leafId) {
774
798
  this.ensureTable();
775
- const leaf = leafId ? this.agent.sql`
776
- SELECT id FROM assistant_messages WHERE id = ${leafId} AND session_id = ${this.sessionId}
777
- `[0] : this.latestLeafRow();
799
+ const leaf = leafId ? this.leafRowById(leafId) : this.latestLeafRow();
778
800
  if (!leaf) return [];
779
- const path = this.agent.sql`
780
- WITH RECURSIVE path AS (
781
- SELECT *, 0 as depth FROM assistant_messages WHERE id = ${leaf.id}
782
- UNION ALL
783
- SELECT m.*, p.depth + 1 FROM assistant_messages m
784
- JOIN path p ON m.id = p.parent_id
785
- WHERE m.session_id = ${this.sessionId} AND p.depth < 10000
786
- )
787
- SELECT content FROM path ORDER BY depth DESC
788
- `;
789
- const messages = this.parseRows(path);
801
+ const messages = this.messagesByPathStats(this.pathRowStats(leaf.id));
790
802
  const compactions = this.getCompactions();
791
803
  if (compactions.length === 0) return messages;
792
804
  return this.applyCompactions(messages, compactions);
793
805
  }
806
+ getRecentHistory(leafId, maxContentBytes, minRecentMessages = 1) {
807
+ this.ensureTable();
808
+ const leaf = leafId ? this.leafRowById(leafId) : this.latestLeafRow();
809
+ if (!leaf) return {
810
+ messages: [],
811
+ truncated: false,
812
+ totalContentBytes: 0
813
+ };
814
+ const stats = this.pathRowStats(leaf.id);
815
+ const totalContentBytes = stats.reduce((sum, row) => sum + row.bytes, 0);
816
+ const minRecent = Math.max(1, Math.floor(minRecentMessages));
817
+ let start = stats.length - 1;
818
+ let used = stats[start]?.bytes ?? 0;
819
+ while (start > 0 && (stats.length - start < minRecent || used + stats[start - 1].bytes <= maxContentBytes)) {
820
+ start--;
821
+ used += stats[start].bytes;
822
+ }
823
+ const messages = this.messagesByPathStats(stats.slice(start));
824
+ const compactions = this.getCompactions();
825
+ return {
826
+ messages: compactions.length === 0 ? messages : this.applyCompactions(messages, compactions),
827
+ truncated: start > 0,
828
+ totalContentBytes
829
+ };
830
+ }
831
+ getHistoryRowStats(leafId) {
832
+ this.ensureTable();
833
+ const leaf = leafId ? this.leafRowById(leafId) : this.latestLeafRow();
834
+ return leaf ? this.pathRowStats(leaf.id) : [];
835
+ }
794
836
  getLatestLeaf() {
795
837
  this.ensureTable();
796
838
  const row = this.latestLeafRow();
797
- return row ? this.parse(row.content) : null;
839
+ return row ? this.getMessage(row.id) : null;
798
840
  }
799
841
  getBranches(messageId) {
800
842
  this.ensureTable();
@@ -910,12 +952,80 @@ var AgentSessionProvider = class {
910
952
  }
911
953
  latestLeafRow() {
912
954
  return this.agent.sql`
913
- SELECT m.id, m.content FROM assistant_messages m
955
+ SELECT m.id FROM assistant_messages m
914
956
  LEFT JOIN assistant_messages c ON c.parent_id = m.id AND c.session_id = ${this.sessionId}
915
957
  WHERE c.id IS NULL AND m.session_id = ${this.sessionId}
916
958
  ORDER BY m.created_at DESC, m.rowid DESC LIMIT 1
917
959
  `[0] ?? null;
918
960
  }
961
+ leafRowById(leafId) {
962
+ return this.agent.sql`
963
+ SELECT id FROM assistant_messages WHERE id = ${leafId} AND session_id = ${this.sessionId}
964
+ `[0] ?? null;
965
+ }
966
+ /**
967
+ * The active branch path as (id, role, content size) rows, root → leaf.
968
+ *
969
+ * Recurses over (id, parent_id) only. Carrying `content` through the
970
+ * recursive queue AND the ORDER BY sorter materializes the entire
971
+ * transcript several times over inside SQLite's allocator, which in
972
+ * workerd shares the isolate's memory budget with the JS heap — large
973
+ * media-heavy sessions then fail with SQLITE_NOMEM on wake (#1710).
974
+ * Content is fetched separately in bounded chunks (`messagesByPathStats`).
975
+ */
976
+ pathRowStats(leafId) {
977
+ return this.agent.sql`
978
+ WITH RECURSIVE path(id, parent_id, depth) AS (
979
+ SELECT id, parent_id, 0 FROM assistant_messages WHERE id = ${leafId}
980
+ UNION ALL
981
+ SELECT m.id, m.parent_id, p.depth + 1 FROM assistant_messages m
982
+ JOIN path p ON m.id = p.parent_id
983
+ WHERE m.session_id = ${this.sessionId} AND p.depth < 10000
984
+ )
985
+ SELECT path.id AS id, am.role AS role, LENGTH(CAST(am.content AS BLOB)) AS bytes
986
+ FROM path JOIN assistant_messages am ON am.id = path.id
987
+ ORDER BY path.depth DESC
988
+ `;
989
+ }
990
+ /**
991
+ * Fetch and parse message content for an ordered list of path rows.
992
+ *
993
+ * Content is read in chunks bounded by both row count and cumulative
994
+ * stored bytes (no ORDER BY — SQLite streams rows without materializing
995
+ * the result set) and reassembled in path order. Rows that fail to parse
996
+ * are skipped, matching previous behavior.
997
+ */
998
+ messagesByPathStats(rows) {
999
+ const contentById = /* @__PURE__ */ new Map();
1000
+ const fetchChunk = (ids) => {
1001
+ const fetched = this.agent.sql`
1002
+ SELECT id, content FROM assistant_messages
1003
+ WHERE session_id = ${this.sessionId}
1004
+ AND id IN (SELECT value FROM json_each(${JSON.stringify(ids)}))
1005
+ `;
1006
+ for (const row of fetched) contentById.set(row.id, row.content);
1007
+ };
1008
+ let chunk = [];
1009
+ let chunkBytes = 0;
1010
+ for (const row of rows) {
1011
+ if (chunk.length > 0 && (chunk.length >= HISTORY_CONTENT_CHUNK_SIZE || chunkBytes + row.bytes > HISTORY_CONTENT_CHUNK_BYTES)) {
1012
+ fetchChunk(chunk);
1013
+ chunk = [];
1014
+ chunkBytes = 0;
1015
+ }
1016
+ chunk.push(row.id);
1017
+ chunkBytes += row.bytes;
1018
+ }
1019
+ if (chunk.length > 0) fetchChunk(chunk);
1020
+ const result = [];
1021
+ for (const row of rows) {
1022
+ const content = contentById.get(row.id);
1023
+ if (content === void 0) continue;
1024
+ const msg = this.parse(content);
1025
+ if (msg) result.push(msg);
1026
+ }
1027
+ return result;
1028
+ }
919
1029
  indexFTS(message) {
920
1030
  const text = message.parts.filter((p) => p.type === "text").map((p) => p.text).join(" ");
921
1031
  this.deleteFTS(message.id);
@@ -1023,6 +1133,8 @@ var Session = class Session {
1023
1133
  constructor(storage, options) {
1024
1134
  this._warnedCompactionNoOp = false;
1025
1135
  this._ready = false;
1136
+ this._skillScanRan = false;
1137
+ this._warnedNoRecentHistorySupport = false;
1026
1138
  this.storage = storage;
1027
1139
  this.context = new ContextBlocks(options?.context ?? [], options?.promptStore);
1028
1140
  this._tokenCounter = options?.tokenCounter;
@@ -1171,12 +1283,37 @@ var Session = class Session {
1171
1283
  * for load_context tool results that haven't been unloaded.
1172
1284
  * Runs once per init to survive hibernation / eviction, including for
1173
1285
  * async SessionProviders (e.g. Postgres) where we must `await` history.
1286
+ *
1287
+ * Skipped entirely when no skill-capable provider is configured —
1288
+ * `load_context` results can only exist when a skill block was registered,
1289
+ * and the scan would otherwise read the whole transcript on every wake,
1290
+ * bypassing byte-budgeted hydration (#1710). A skill block added later via
1291
+ * `addContext()` triggers the scan at that point instead.
1174
1292
  */
1175
1293
  async _restoreLoadedSkills() {
1176
- const history = await this.storage.getHistory();
1294
+ if (!this.context.hasSkillCapableConfigs()) return;
1295
+ await this._scanHistoryForLoadedSkills();
1296
+ }
1297
+ /**
1298
+ * Scan stored history for load/unload_context tool results and restore
1299
+ * the loaded-skill tracking set.
1300
+ *
1301
+ * Memory-bounded when the provider supports `getHistoryRowStats`: rows
1302
+ * are enumerated without content, then only assistant rows are fetched
1303
+ * and scanned ONE AT A TIME — peak memory is a single message instead of
1304
+ * the whole transcript. Falls back to a full `getHistory()` read for
1305
+ * providers without row stats (e.g. Postgres).
1306
+ *
1307
+ * Note: the bounded path scans raw path rows, so `load_context` results
1308
+ * inside compacted ranges are still seen (the full-read path hides them
1309
+ * behind compaction overlays). That superset is intentional — the stored
1310
+ * tool result still exists and can be reclaimed by `unloadSkill`.
1311
+ */
1312
+ async _scanHistoryForLoadedSkills() {
1313
+ this._skillScanRan = true;
1177
1314
  const loaded = /* @__PURE__ */ new Set();
1178
- for (const msg of history) {
1179
- if (msg.role !== "assistant") continue;
1315
+ const scanMessage = (msg) => {
1316
+ if (msg.role !== "assistant") return;
1180
1317
  for (const part of msg.parts) if (part.toolName === "load_context" && part.state === "output-available") {
1181
1318
  const input = part.input;
1182
1319
  if (input?.label && input?.key) {
@@ -1188,7 +1325,15 @@ var Session = class Session {
1188
1325
  const input = part.input;
1189
1326
  if (input?.label && input?.key) loaded.delete(`${input.label}:${input.key}`);
1190
1327
  }
1191
- }
1328
+ };
1329
+ if (this.storage.getHistoryRowStats) {
1330
+ const stats = await this.storage.getHistoryRowStats();
1331
+ for (const row of stats) {
1332
+ if (row.role !== "assistant") continue;
1333
+ const msg = await this.storage.getMessage(row.id);
1334
+ if (msg) scanMessage(msg);
1335
+ }
1336
+ } else for (const msg of await this.storage.getHistory()) scanMessage(msg);
1192
1337
  if (loaded.size > 0) this.context.restoreLoadedSkills(loaded);
1193
1338
  }
1194
1339
  /**
@@ -1238,6 +1383,45 @@ var Session = class Session {
1238
1383
  await this._ensureRestored();
1239
1384
  return this.storage.getHistory(leafId);
1240
1385
  }
1386
+ /**
1387
+ * Byte-budgeted read of the most recent messages on the active branch
1388
+ * path (always at least the leaf message, and at least
1389
+ * `minRecentMessages` when the path is long enough). Lets hosts hydrate
1390
+ * a bounded window instead of the full transcript so wake-time memory
1391
+ * scales with the budget rather than total session history (#1710).
1392
+ *
1393
+ * Falls back to a full (untruncated) read when the provider doesn't
1394
+ * implement `getRecentHistory`. The fallback reports honest metadata
1395
+ * (`truncated: false` and the real serialized size) and warns once so a
1396
+ * host relying on the budget knows it is not being enforced.
1397
+ */
1398
+ async getRecentHistory(maxContentBytes, minRecentMessages = 1) {
1399
+ await this._ensureRestored();
1400
+ if (this.storage.getRecentHistory) return this.storage.getRecentHistory(null, maxContentBytes, minRecentMessages);
1401
+ if (!this._warnedNoRecentHistorySupport) {
1402
+ this._warnedNoRecentHistorySupport = true;
1403
+ console.warn("[Session] The configured SessionProvider does not implement getRecentHistory; the requested byte budget cannot be enforced and the FULL history was loaded. Implement getRecentHistory (and getHistoryRowStats) on the provider to bound hydration.");
1404
+ }
1405
+ const messages = await this.storage.getHistory();
1406
+ let totalContentBytes = 0;
1407
+ for (const message of messages) totalContentBytes += JSON.stringify(message).length;
1408
+ return {
1409
+ messages,
1410
+ truncated: false,
1411
+ totalContentBytes
1412
+ };
1413
+ }
1414
+ /**
1415
+ * Per-row stored sizes for the active branch path (root → leaf) WITHOUT
1416
+ * loading message content, or `null` when the provider doesn't support it.
1417
+ * Lets hosts find oversized rows (e.g. inline base64 media) and process
1418
+ * them one at a time with bounded memory.
1419
+ */
1420
+ async getHistoryRowStats() {
1421
+ await this._ensureRestored();
1422
+ if (!this.storage.getHistoryRowStats) return null;
1423
+ return this.storage.getHistoryRowStats();
1424
+ }
1241
1425
  async getMessage(id) {
1242
1426
  await this._ensureRestored();
1243
1427
  return this.storage.getMessage(id);
@@ -1351,6 +1535,25 @@ var Session = class Session {
1351
1535
  message
1352
1536
  });
1353
1537
  }
1538
+ /**
1539
+ * @internal
1540
+ * Rewrite a stored message WITHOUT the public-write side effects: no
1541
+ * token-estimate status broadcast (which reads the FULL history) and no
1542
+ * auto-compaction check. For framework maintenance passes that rewrite
1543
+ * many rows with bounded memory — e.g. media eviction (#1710) — where the
1544
+ * per-row full-history estimate would reintroduce the memory pressure the
1545
+ * pass exists to remove. The message-change listener still fires so a
1546
+ * cache-owning host stays coherent. Application code should use
1547
+ * `updateMessage`.
1548
+ */
1549
+ async internal_rewriteMessage(message) {
1550
+ await this._ensureRestored();
1551
+ await this.storage.updateMessage(message);
1552
+ await this._notifyMessagesChanged({
1553
+ type: "update",
1554
+ message
1555
+ });
1556
+ }
1354
1557
  async deleteMessages(messageIds) {
1355
1558
  await this._ensureRestored();
1356
1559
  await this.storage.deleteMessages(messageIds);
@@ -1452,12 +1655,14 @@ var Session = class Session {
1452
1655
  const key = this._sessionId ? `${label}_${this._sessionId}` : label;
1453
1656
  provider = new AgentContextProvider(this._agent, key);
1454
1657
  }
1455
- return this.context.addBlock({
1658
+ const block = await this.context.addBlock({
1456
1659
  label,
1457
1660
  description: opts.description,
1458
1661
  maxTokens: opts.maxTokens,
1459
1662
  provider
1460
1663
  });
1664
+ if (block.isSkill && !this._skillScanRan) await this._scanHistoryForLoadedSkills();
1665
+ return block;
1461
1666
  }
1462
1667
  /**
1463
1668
  * Remove a dynamically registered context block.