@circuitwall/jarela 2.3.0 → 2.4.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 (208) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +4 -4
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  13. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  14. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/api/v1/access/[identity]/route.js +7 -0
  19. package/.next/standalone/.next/server/app/api/v1/access/route.js +7 -0
  20. package/.next/standalone/.next/server/app/api/v1/agents/[id]/compact/route.js +7 -0
  21. package/.next/standalone/.next/server/app/api/v1/agents/[id]/display-filters/route.js +17 -5
  22. package/.next/standalone/.next/server/app/api/v1/agents/[id]/route.js +7 -0
  23. package/.next/standalone/.next/server/app/api/v1/agents/[id]/thread/route.js +17 -5
  24. package/.next/standalone/.next/server/app/api/v1/agents/route.js +7 -0
  25. package/.next/standalone/.next/server/app/api/v1/allowed-sites/[hostname]/route.js +7 -0
  26. package/.next/standalone/.next/server/app/api/v1/allowed-sites/route.js +7 -0
  27. package/.next/standalone/.next/server/app/api/v1/artifacts/lifecycle/route.js +7 -0
  28. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js +26 -32
  29. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/ignores/[remote_jid]/route.js +18 -7
  31. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/ignores/route.js +18 -7
  32. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js +26 -32
  33. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js +27 -33
  35. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +26 -32
  37. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/routes/[route_id]/route.js +28 -12
  39. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/routes/route.js +28 -12
  40. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js +26 -32
  41. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/v1/bridges/kinds/route.js +9 -2
  43. package/.next/standalone/.next/server/app/api/v1/bridges/kinds/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/v1/bridges/route.js +19 -7
  45. package/.next/standalone/.next/server/app/api/v1/bridges/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/v1/dashboard/metrics/route.js +17 -5
  47. package/.next/standalone/.next/server/app/api/v1/dashboard/pricing/route.js +7 -0
  48. package/.next/standalone/.next/server/app/api/v1/documents/search/route.js +7 -0
  49. package/.next/standalone/.next/server/app/api/v1/documents/settings/route.js +7 -0
  50. package/.next/standalone/.next/server/app/api/v1/env-sync/allowlist/route.js +7 -0
  51. package/.next/standalone/.next/server/app/api/v1/env-sync/route.js +7 -0
  52. package/.next/standalone/.next/server/app/api/v1/events/route.js +26 -32
  53. package/.next/standalone/.next/server/app/api/v1/events/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/v1/extension/browser/activate/route.js +7 -0
  55. package/.next/standalone/.next/server/app/api/v1/extension/browser/foreground/route.js +7 -0
  56. package/.next/standalone/.next/server/app/api/v1/extension/browser/history/route.js +7 -0
  57. package/.next/standalone/.next/server/app/api/v1/extension/browser/poll/route.js +7 -0
  58. package/.next/standalone/.next/server/app/api/v1/extension/browser/progress/route.js +7 -0
  59. package/.next/standalone/.next/server/app/api/v1/extension/browser/result/route.js +7 -0
  60. package/.next/standalone/.next/server/app/api/v1/extension/browser/retry/route.js +7 -0
  61. package/.next/standalone/.next/server/app/api/v1/extension/browser/status/route.js +7 -0
  62. package/.next/standalone/.next/server/app/api/v1/extension/browser/tabs/route.js +7 -0
  63. package/.next/standalone/.next/server/app/api/v1/harnesses/[id]/route.js +7 -0
  64. package/.next/standalone/.next/server/app/api/v1/harnesses/default/route.js +7 -0
  65. package/.next/standalone/.next/server/app/api/v1/harnesses/route.js +7 -0
  66. package/.next/standalone/.next/server/app/api/v1/health/route.js +17 -5
  67. package/.next/standalone/.next/server/app/api/v1/integrations/[name]/route.js +7 -0
  68. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/callback/route.js +7 -0
  69. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/start/route.js +7 -0
  70. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/status/route.js +7 -0
  71. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/callback/route.js +7 -0
  72. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/start/route.js +7 -0
  73. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/status/route.js +7 -0
  74. package/.next/standalone/.next/server/app/api/v1/integrations/route.js +7 -0
  75. package/.next/standalone/.next/server/app/api/v1/lifecycle/adoption/route.js +17 -5
  76. package/.next/standalone/.next/server/app/api/v1/maps/embed/route.js +7 -0
  77. package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js +17 -5
  78. package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js +17 -5
  79. package/.next/standalone/.next/server/app/api/v1/memory/[namespace]/[key]/route.js +7 -0
  80. package/.next/standalone/.next/server/app/api/v1/memory/policy/route.js +7 -0
  81. package/.next/standalone/.next/server/app/api/v1/memory/route.js +7 -0
  82. package/.next/standalone/.next/server/app/api/v1/memory/search/route.js +7 -0
  83. package/.next/standalone/.next/server/app/api/v1/models/[name]/compact-threads/route.js +7 -0
  84. package/.next/standalone/.next/server/app/api/v1/models/[name]/route.js +7 -0
  85. package/.next/standalone/.next/server/app/api/v1/models/route.js +7 -0
  86. package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js +27 -12
  87. package/.next/standalone/.next/server/app/api/v1/profile/location/route.js +7 -0
  88. package/.next/standalone/.next/server/app/api/v1/profile/route.js +7 -0
  89. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +7 -0
  90. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js +7 -0
  91. package/.next/standalone/.next/server/app/api/v1/providers/github-copilot/auth/route.js +7 -0
  92. package/.next/standalone/.next/server/app/api/v1/providers/route.js +7 -0
  93. package/.next/standalone/.next/server/app/api/v1/proxy-config/route.js +7 -0
  94. package/.next/standalone/.next/server/app/api/v1/redaction/init/route.js +7 -0
  95. package/.next/standalone/.next/server/app/api/v1/redaction/route.js +7 -0
  96. package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/[id]/route.js +17 -5
  97. package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/route.js +7 -0
  98. package/.next/standalone/.next/server/app/api/v1/security/idle-timeout/route.js +7 -0
  99. package/.next/standalone/.next/server/app/api/v1/security/pin/route.js +7 -0
  100. package/.next/standalone/.next/server/app/api/v1/security/state/route.js +7 -0
  101. package/.next/standalone/.next/server/app/api/v1/security/unlock/route.js +7 -0
  102. package/.next/standalone/.next/server/app/api/v1/security/verify-pin/route.js +7 -0
  103. package/.next/standalone/.next/server/app/api/v1/skills/[id]/route.js +7 -0
  104. package/.next/standalone/.next/server/app/api/v1/skills/repos/[id]/route.js +7 -0
  105. package/.next/standalone/.next/server/app/api/v1/skills/repos/route.js +7 -0
  106. package/.next/standalone/.next/server/app/api/v1/skills/route.js +7 -0
  107. package/.next/standalone/.next/server/app/api/v1/tasks/[agent_id]/route.js +7 -0
  108. package/.next/standalone/.next/server/app/api/v1/tasks/route.js +7 -0
  109. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/activity/route.js +7 -0
  110. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/context-pin/route.js +12 -6
  111. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js +7 -0
  112. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +0 -1
  113. package/.next/standalone/.next/server/app/api/v1/threads/route.js +17 -5
  114. package/.next/standalone/.next/server/app/api/v1/update/apply/route.js.nft.json +1 -1
  115. package/.next/standalone/.next/server/app/api/v1/voice/transcribe/route.js +17 -5
  116. package/.next/standalone/.next/server/app/api/v1/voice/tts/route.js +17 -5
  117. package/.next/standalone/.next/server/app/api/v1/watchers/[id]/route.js +17 -5
  118. package/.next/standalone/.next/server/app/page.js +322 -275
  119. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/app/setup/page.js +8 -0
  121. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  122. package/.next/standalone/.next/server/app-paths-manifest.json +4 -4
  123. package/.next/standalone/.next/server/chunks/1388.js +106 -36
  124. package/.next/standalone/.next/server/chunks/2068.js +47 -10
  125. package/.next/standalone/.next/server/chunks/210.js +6 -6
  126. package/.next/standalone/.next/server/chunks/2447.js +10 -5
  127. package/.next/standalone/.next/server/chunks/2471.js +17 -17
  128. package/.next/standalone/.next/server/chunks/3042.js +10 -8
  129. package/.next/standalone/.next/server/chunks/{4540.js → 309.js} +104 -38
  130. package/.next/standalone/.next/server/chunks/{2350.js → 3272.js} +104 -37
  131. package/.next/standalone/.next/server/chunks/3399.js +525 -525
  132. package/.next/standalone/.next/server/chunks/3524.js +150 -67
  133. package/.next/standalone/.next/server/chunks/4189.js +1 -1
  134. package/.next/standalone/.next/server/chunks/4508.js +2 -2
  135. package/.next/standalone/.next/server/chunks/46.js +73 -28
  136. package/.next/standalone/.next/server/chunks/5908.js +63 -42
  137. package/.next/standalone/.next/server/chunks/6142.js +10 -2
  138. package/.next/standalone/.next/server/chunks/6649.js +1 -1
  139. package/.next/standalone/.next/server/chunks/8461.js +10 -8
  140. package/.next/standalone/.next/server/chunks/9032.js +5 -5
  141. package/.next/standalone/.next/server/chunks/9164.js +435 -176
  142. package/.next/standalone/.next/server/chunks/9304.js +51 -12
  143. package/.next/standalone/.next/server/chunks/{423.js → 9590.js} +66 -21
  144. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  145. package/.next/standalone/.next/server/middleware.js +71 -18
  146. package/.next/standalone/.next/server/pages/404.html +2 -2
  147. package/.next/standalone/.next/server/pages/500.html +1 -1
  148. package/.next/standalone/.next/server/preview-props.json +3 -3
  149. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  150. package/.next/standalone/.next/static/chunks/app/{page-213a623b37226452.js → page-04748847c6ff866f.js} +171 -132
  151. package/.next/standalone/.next/static/css/{312542a5725ba0d5.css → 2669e8744cc8c212.css} +2 -2
  152. package/.next/standalone/docs/adoption/changesets.json +49 -0
  153. package/.next/standalone/package.json +1 -1
  154. package/CHANGELOG.md +19 -0
  155. package/app/api/v1/threads/[thread_id]/run/route.ts +0 -1
  156. package/components/chat/MessageBubble.tsx +12 -0
  157. package/components/integrations/NetworkSection.tsx +2 -2
  158. package/lib/agents/citation-checker.test.ts +40 -2
  159. package/lib/agents/citation-checker.ts +56 -11
  160. package/lib/agents/context-boundary.ts +5 -6
  161. package/lib/agents/instruction-edits.test.ts +120 -0
  162. package/lib/agents/instruction-edits.ts +65 -42
  163. package/lib/agents/prepare/request.ts +0 -2
  164. package/lib/agents/prepare/system-prompt.test.ts +10 -0
  165. package/lib/agents/prepare/system-prompt.ts +8 -2
  166. package/lib/agents/proposals.test.ts +76 -0
  167. package/lib/agents/proposals.ts +13 -5
  168. package/lib/agents/run-thread.auto-boundary.test.ts +125 -5
  169. package/lib/agents/run-thread.ts +24 -9
  170. package/lib/agents/sqlite-checkpoint-saver.test.ts +91 -0
  171. package/lib/agents/warm-summary-background.ts +22 -2
  172. package/lib/agents/warm-topics.test.ts +155 -1
  173. package/lib/agents/warm-topics.ts +27 -7
  174. package/lib/bridges/dispatcher.ts +1 -0
  175. package/lib/bridges/message-role.test.ts +81 -0
  176. package/lib/bridges/message-role.ts +23 -10
  177. package/lib/bridges/types.ts +6 -0
  178. package/lib/bridges/whatsapp-link.test.ts +32 -0
  179. package/lib/bridges/whatsapp-link.ts +21 -0
  180. package/lib/bridges/whatsapp.inbound.test.ts +239 -0
  181. package/lib/bridges/whatsapp.sender.test.ts +80 -1
  182. package/lib/bridges/whatsapp.ts +76 -38
  183. package/lib/pricing/snapshot.test.ts +70 -0
  184. package/lib/pricing/snapshot.ts +10 -2
  185. package/lib/proxy/dispatcher.test.ts +119 -1
  186. package/lib/proxy/dispatcher.ts +22 -9
  187. package/lib/stores/agent-configs.ts +10 -5
  188. package/lib/stores/bridges.test.ts +81 -0
  189. package/lib/stores/bridges.ts +9 -8
  190. package/lib/stores/proxy-config.test.ts +120 -0
  191. package/lib/stores/proxy-config.ts +22 -4
  192. package/lib/stores/threads.ts +12 -8
  193. package/lib/tools/communications/calendar.test.ts +43 -2
  194. package/lib/tools/communications/calendar.ts +2 -0
  195. package/lib/tools/communications/gmail.test.ts +51 -1
  196. package/lib/tools/communications/gmail.ts +44 -2
  197. package/lib/tools/communications/outlook-calendar.ts +2 -0
  198. package/lib/tools/communications/outlook.test.ts +33 -2
  199. package/lib/tools/communications/outlook.ts +2 -0
  200. package/lib/tools/filesystem/edit-match.test.ts +133 -0
  201. package/lib/tools/filesystem/edit-match.ts +7 -2
  202. package/lib/tools/support/result-refs.test.ts +131 -0
  203. package/lib/tools/support/result-refs.ts +41 -9
  204. package/lib/utils/source-link.test.ts +51 -0
  205. package/lib/utils/source-link.ts +45 -0
  206. package/package.json +1 -1
  207. /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → hwujW4hR1b-r3k5WdMxZO}/_buildManifest.js +0 -0
  208. /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → hwujW4hR1b-r3k5WdMxZO}/_ssgManifest.js +0 -0
@@ -677,10 +677,11 @@ function isIntegrationProbe(name) {
677
677
  /* harmony export */ VJ: () => (/* binding */ _resolveOutlookAuth)
678
678
  /* harmony export */ });
679
679
  /* unused harmony exports outlookSearchTool, outlookGetMessageTool, outlookListFoldersTool, outlookModifyMessageTool, outlookCreateDraftTool, outlookTrashMessageTool, outlookSendEmailTool */
680
- /* harmony import */ var _langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(49585);
681
- /* harmony import */ var zod__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(17033);
682
- /* harmony import */ var _lib_utils_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(85212);
683
- /* harmony import */ var _lib_utils_text__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(69494);
680
+ /* harmony import */ var _langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(71966);
681
+ /* harmony import */ var zod__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(17033);
682
+ /* harmony import */ var _lib_utils_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(85212);
683
+ /* harmony import */ var _lib_utils_text__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(69494);
684
+ /* harmony import */ var _lib_utils_source_link__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(76385);
684
685
  /* harmony import */ var _packages_langchain_package__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(18932);
685
686
  /* harmony import */ var _lib_integrations_microsoft_oauth__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(46291);
686
687
  /**
@@ -700,6 +701,7 @@ function isIntegrationProbe(name) {
700
701
 
701
702
 
702
703
 
704
+
703
705
  // Exposed for the integrations test endpoint.
704
706
  function _resolveOutlookAuth() {
705
707
  return (0,_lib_integrations_microsoft_oauth__WEBPACK_IMPORTED_MODULE_2__/* .resolveMicrosoftAuth */ .jG)();
@@ -725,7 +727,8 @@ function summarizeMessage(m) {
725
727
  categories: m.categories ?? [],
726
728
  folder_id: m.parentFolderId ?? null,
727
729
  snippet: m.bodyPreview ?? null,
728
- web_link: m.webLink ?? null
730
+ web_link: m.webLink ?? null,
731
+ cite_as: (0,_lib_utils_source_link__WEBPACK_IMPORTED_MODULE_3__/* .citeAs */ .w)(m.subject, m.webLink, "Outlook message")
729
732
  };
730
733
  }
731
734
  const BODY_CAP = 30000;
@@ -761,9 +764,9 @@ const outlookSearchTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/
761
764
  }, {
762
765
  name: "outlook_search",
763
766
  description: "Search the user's Outlook mailbox using KQL (Microsoft's keyword query language). " + "Returns message metadata (from, to, subject, received, snippet, labels) plus @odata.count " + "and next_link when available. Examples: " + "'from:notifications@github.com', 'subject:invoice hasAttachment:true', " + "'isRead:false received>=2026-05-17'. **Use this before calling outlook_get_message** — " + "the snippet is often enough.",
764
- schema: zod__WEBPACK_IMPORTED_MODULE_3__/* .object */ .Ikc({
765
- query: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().describe("KQL query string"),
766
- max_results: zod__WEBPACK_IMPORTED_MODULE_3__/* .number */ .aig().int().optional().describe("Max messages (default 25, max 100)")
767
+ schema: zod__WEBPACK_IMPORTED_MODULE_4__/* .object */ .Ikc({
768
+ query: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().describe("KQL query string"),
769
+ max_results: zod__WEBPACK_IMPORTED_MODULE_4__/* .number */ .aig().int().optional().describe("Max messages (default 25, max 100)")
767
770
  })
768
771
  });
769
772
  const outlookGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ id })=>{
@@ -775,10 +778,10 @@ const outlookGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_
775
778
  if (m.error) return JSON.stringify(m);
776
779
  const summary = summarizeMessage(m);
777
780
  const rawBody = m.body?.content ?? "";
778
- const plain = m.body?.contentType === "html" ? (0,_lib_utils_html__WEBPACK_IMPORTED_MODULE_4__/* .stripHtml */ .Q)(rawBody, {
781
+ const plain = m.body?.contentType === "html" ? (0,_lib_utils_html__WEBPACK_IMPORTED_MODULE_5__/* .stripHtml */ .Q)(rawBody, {
779
782
  preserveParagraphs: true
780
783
  }) : rawBody;
781
- const { text: body, truncated } = (0,_lib_utils_text__WEBPACK_IMPORTED_MODULE_5__/* .truncateBytes */ .Q)(plain, BODY_CAP);
784
+ const { text: body, truncated } = (0,_lib_utils_text__WEBPACK_IMPORTED_MODULE_6__/* .truncateBytes */ .Q)(plain, BODY_CAP);
782
785
  return JSON.stringify({
783
786
  ...summary,
784
787
  body,
@@ -787,8 +790,8 @@ const outlookGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_
787
790
  }, {
788
791
  name: "outlook_get_message",
789
792
  description: "Fetch one Outlook message by id, including full headers and the decoded text body " + "(HTML stripped to plain text when needed). Body capped at 30KB with `truncated:true` " + "when cut off.",
790
- schema: zod__WEBPACK_IMPORTED_MODULE_3__/* .object */ .Ikc({
791
- id: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().describe("Outlook message id (from outlook_search results)")
793
+ schema: zod__WEBPACK_IMPORTED_MODULE_4__/* .object */ .Ikc({
794
+ id: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().describe("Outlook message id (from outlook_search results)")
792
795
  })
793
796
  });
794
797
  const outlookListFoldersTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ()=>{
@@ -814,7 +817,7 @@ const outlookListFoldersTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE
814
817
  }, {
815
818
  name: "outlook_list_folders",
816
819
  description: "List all Outlook mail folders. Use the `id` (or `well_known` name like 'inbox', " + "'drafts', 'sentitems', 'deleteditems', 'archive') with outlook_modify_message to move " + "a message between folders.",
817
- schema: zod__WEBPACK_IMPORTED_MODULE_3__/* .object */ .Ikc({})
820
+ schema: zod__WEBPACK_IMPORTED_MODULE_4__/* .object */ .Ikc({})
818
821
  });
819
822
  const outlookModifyMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ id, mark_read, move_to_folder })=>{
820
823
  const auth = (0,_lib_integrations_microsoft_oauth__WEBPACK_IMPORTED_MODULE_2__/* .resolveMicrosoftAuth */ .jG)();
@@ -857,10 +860,10 @@ const outlookModifyMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODU
857
860
  }, {
858
861
  name: "outlook_modify_message",
859
862
  description: "Modify an Outlook message: mark read/unread and/or move to another folder. Use " + "`mark_read: true` to mark read, `mark_read: false` to mark unread. `move_to_folder` " + "accepts a folder id from outlook_list_folders or a well-known name (e.g. 'archive', " + "'deleteditems'). Note that moving returns a NEW message id because Graph clones the " + "item into the destination folder.",
860
- schema: zod__WEBPACK_IMPORTED_MODULE_3__/* .object */ .Ikc({
861
- id: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().describe("Outlook message id"),
862
- mark_read: zod__WEBPACK_IMPORTED_MODULE_3__/* .boolean */ .zMY().optional().describe("Mark read (true) or unread (false)"),
863
- move_to_folder: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().optional().describe("Destination folder id or well-known name")
863
+ schema: zod__WEBPACK_IMPORTED_MODULE_4__/* .object */ .Ikc({
864
+ id: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().describe("Outlook message id"),
865
+ mark_read: zod__WEBPACK_IMPORTED_MODULE_4__/* .boolean */ .zMY().optional().describe("Mark read (true) or unread (false)"),
866
+ move_to_folder: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().optional().describe("Destination folder id or well-known name")
864
867
  })
865
868
  });
866
869
  const outlookCreateDraftTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ to, cc, subject, body, content_type })=>{
@@ -899,12 +902,12 @@ const outlookCreateDraftTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE
899
902
  }, {
900
903
  name: "outlook_create_draft",
901
904
  description: "Create an Outlook draft email (saved to the Drafts folder, NOT sent). The user opens " + "Outlook to review and click Send. `to` and `cc` are arrays of plain email addresses. " + "`content_type` defaults to 'text'; pass 'html' if `body` contains HTML markup.",
902
- schema: zod__WEBPACK_IMPORTED_MODULE_3__/* .object */ .Ikc({
903
- to: zod__WEBPACK_IMPORTED_MODULE_3__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().email()).min(1).describe("Primary recipient emails"),
904
- cc: zod__WEBPACK_IMPORTED_MODULE_3__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().email()).optional().describe("CC recipient emails"),
905
- subject: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().optional().describe("Subject line"),
906
- body: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().max(DRAFT_BODY_MAX_CHARS).optional().describe("Email body (max 100,000 characters)"),
907
- content_type: zod__WEBPACK_IMPORTED_MODULE_3__/* ["enum"] */ .k5n([
905
+ schema: zod__WEBPACK_IMPORTED_MODULE_4__/* .object */ .Ikc({
906
+ to: zod__WEBPACK_IMPORTED_MODULE_4__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().email()).min(1).describe("Primary recipient emails"),
907
+ cc: zod__WEBPACK_IMPORTED_MODULE_4__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().email()).optional().describe("CC recipient emails"),
908
+ subject: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().optional().describe("Subject line"),
909
+ body: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().max(DRAFT_BODY_MAX_CHARS).optional().describe("Email body (max 100,000 characters)"),
910
+ content_type: zod__WEBPACK_IMPORTED_MODULE_4__/* ["enum"] */ .k5n([
908
911
  "text",
909
912
  "html"
910
913
  ]).optional().describe("Body content type (default 'text')")
@@ -933,8 +936,8 @@ const outlookTrashMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODUL
933
936
  }, {
934
937
  name: "outlook_trash_message",
935
938
  description: "Move an Outlook message to Deleted Items (recoverable from the trash for 30 days). " + "Does NOT permanently delete. Returns the new message id assigned after the move.",
936
- schema: zod__WEBPACK_IMPORTED_MODULE_3__/* .object */ .Ikc({
937
- id: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().describe("Outlook message id to trash")
939
+ schema: zod__WEBPACK_IMPORTED_MODULE_4__/* .object */ .Ikc({
940
+ id: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().describe("Outlook message id to trash")
938
941
  })
939
942
  });
940
943
  const outlookSendEmailTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ to, cc, bcc, subject, body, content_type, save_to_sent_items })=>{
@@ -982,17 +985,17 @@ const outlookSendEmailTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0
982
985
  }, {
983
986
  name: "outlook_send_email",
984
987
  description: "Send an Outlook email immediately via Microsoft Graph. Only use when the user explicitly asks " + "to send now; otherwise create a draft with outlook_create_draft. Supports text or HTML bodies " + "via `content_type`. If this returns a 403 scope error, ask the user to reconnect Outlook so " + "Mail.Send is granted.",
985
- schema: zod__WEBPACK_IMPORTED_MODULE_3__/* .object */ .Ikc({
986
- to: zod__WEBPACK_IMPORTED_MODULE_3__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().email()).min(1).describe("Primary recipient emails"),
987
- cc: zod__WEBPACK_IMPORTED_MODULE_3__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().email()).optional().describe("CC recipient emails"),
988
- bcc: zod__WEBPACK_IMPORTED_MODULE_3__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().email()).optional().describe("BCC recipient emails"),
989
- subject: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().optional().describe("Subject line"),
990
- body: zod__WEBPACK_IMPORTED_MODULE_3__/* .string */ .YjP().max(SEND_BODY_MAX_CHARS).optional().describe("Email body (max 100,000 characters)"),
991
- content_type: zod__WEBPACK_IMPORTED_MODULE_3__/* ["enum"] */ .k5n([
988
+ schema: zod__WEBPACK_IMPORTED_MODULE_4__/* .object */ .Ikc({
989
+ to: zod__WEBPACK_IMPORTED_MODULE_4__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().email()).min(1).describe("Primary recipient emails"),
990
+ cc: zod__WEBPACK_IMPORTED_MODULE_4__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().email()).optional().describe("CC recipient emails"),
991
+ bcc: zod__WEBPACK_IMPORTED_MODULE_4__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().email()).optional().describe("BCC recipient emails"),
992
+ subject: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().optional().describe("Subject line"),
993
+ body: zod__WEBPACK_IMPORTED_MODULE_4__/* .string */ .YjP().max(SEND_BODY_MAX_CHARS).optional().describe("Email body (max 100,000 characters)"),
994
+ content_type: zod__WEBPACK_IMPORTED_MODULE_4__/* ["enum"] */ .k5n([
992
995
  "text",
993
996
  "html"
994
997
  ]).optional().describe("Body content type (default 'text')"),
995
- save_to_sent_items: zod__WEBPACK_IMPORTED_MODULE_3__/* .boolean */ .zMY().optional().describe("Whether Graph should save the message to Sent Items (default true)")
998
+ save_to_sent_items: zod__WEBPACK_IMPORTED_MODULE_4__/* .boolean */ .zMY().optional().describe("Whether Graph should save the message to Sent Items (default true)")
996
999
  })
997
1000
  });
998
1001
  (0,_packages_langchain_package__WEBPACK_IMPORTED_MODULE_1__/* .registerLangChainPackage */ .p)({
@@ -2652,7 +2655,7 @@ __webpack_require__.d(__webpack_exports__, {
2652
2655
  // UNUSED EXPORTS: buildToolTelemetryComplaintIssue, createToolTelemetryGitHubIssue, currentToolFailureMetric, reportToolTelemetryIssueTool
2653
2656
 
2654
2657
  // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/tools/index.js
2655
- var tools = __webpack_require__(49585);
2658
+ var tools = __webpack_require__(71966);
2656
2659
  // EXTERNAL MODULE: ./node_modules/zod/v4/classic/schemas.js + 4 modules
2657
2660
  var schemas = __webpack_require__(17033);
2658
2661
  // EXTERNAL MODULE: external "node:crypto"
@@ -3832,12 +3835,13 @@ function drainQueuedBoundary(threadId) {
3832
3835
  function runBoundaryCompaction(threadId, boundarySeq, options) {
3833
3836
  const baseThread = (0,threads/* getThread */.fG)(threadId);
3834
3837
  const basePinSeq = baseThread?.hot_since_seq ?? null;
3838
+ const baseLock = baseThread?.auto_boundary_locked_until_msg_count ?? 0;
3835
3839
  void (0,thread_compaction_state/* runTrackedCompaction */.PL)(threadId, "auto", boundarySeq, async (attempt)=>{
3836
3840
  await Promise.resolve();
3837
- await commitBoundaryCompaction(threadId, boundarySeq, basePinSeq, options, attempt);
3841
+ await commitBoundaryCompaction(threadId, boundarySeq, basePinSeq, baseLock, options, attempt);
3838
3842
  }).catch((err)=>console.warn(`[context-boundary:auto] compaction failed thread=${threadId}: ${String(err)}`)).finally(()=>drainQueuedBoundary(threadId));
3839
3843
  }
3840
- async function commitBoundaryCompaction(threadId, boundarySeq, basePinSeq, options, attempt) {
3844
+ async function commitBoundaryCompaction(threadId, boundarySeq, basePinSeq, baseLock, options, attempt) {
3841
3845
  const committed = await compactThreadWarmContext(threadId, boundarySeq, {
3842
3846
  expectedHotSinceSeq: basePinSeq,
3843
3847
  alignTopicBoundary: true,
@@ -3845,7 +3849,18 @@ async function commitBoundaryCompaction(threadId, boundarySeq, basePinSeq, optio
3845
3849
  ...options,
3846
3850
  attempt
3847
3851
  });
3848
- if (!committed) return;
3852
+ if (!committed) {
3853
+ // A pass that resolved to no change (the active topic still starts at the
3854
+ // current pin) must not be retried on every following turn either. Skip
3855
+ // it when the boundary or lock changed mid-pass (e.g. the user cleared or
3856
+ // moved the boundary): that newer state wins over this stale pass.
3857
+ const lockUntil = options.autoBoundaryLockedUntilMessageCount;
3858
+ const current = (0,threads/* getThread */.fG)(threadId);
3859
+ if (lockUntil !== undefined && current && (current.hot_since_seq ?? null) === basePinSeq && (current.auto_boundary_locked_until_msg_count ?? 0) === baseLock && lockUntil > baseLock) {
3860
+ (0,threads/* setAutoBoundaryLock */.NU)(threadId, lockUntil);
3861
+ }
3862
+ return;
3863
+ }
3849
3864
  console.info(`[context-boundary:auto] thread=${threadId} committed boundary_seq=${committed.boundarySeq} warm_msgs=${committed.sourceMessages}`);
3850
3865
  }
3851
3866
  async function findTopicBoundary(threadId, boundarySeq) {
@@ -4938,7 +4953,7 @@ __webpack_require__.d(__webpack_exports__, {
4938
4953
  // UNUSED EXPORTS: allBuiltins, builtinNames, getAllTools, getBuiltinToolNames, getPackagesDir, getToolCategory, getToolCredentialsRequired, getToolIntegration, getToolSource, getToolsDir, loadExternal, loadLangChainPackages, registerTools, reloadLangChainPackages, toCatalogEntry
4939
4954
 
4940
4955
  // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/tools/index.js
4941
- var tools = __webpack_require__(49585);
4956
+ var tools = __webpack_require__(71966);
4942
4957
  // EXTERNAL MODULE: ./lib/env/config.ts
4943
4958
  var env_config = __webpack_require__(62207);
4944
4959
  // EXTERNAL MODULE: ./lib/cache/keyed-cache.ts
@@ -7187,10 +7202,13 @@ function fuzzyUniqueRange(raw, oldString) {
7187
7202
  if (oldLines.some((line)=>line.length === 0)) return [];
7188
7203
  const rawLines = raw.split(/\r\n|\r|\n/);
7189
7204
  const rawLineStarts = [];
7205
+ const eol = /\r\n|\r|\n/g;
7190
7206
  let offset = 0;
7191
7207
  for (const line of rawLines){
7192
7208
  rawLineStarts.push(offset);
7193
- offset += line.length + 1;
7209
+ eol.lastIndex = offset + line.length;
7210
+ const brk = eol.exec(raw);
7211
+ offset = offset + line.length + (brk ? brk[0].length : 0);
7194
7212
  }
7195
7213
  const scores = [];
7196
7214
  for(let i = 0; i + oldLines.length <= rawLines.length; i++){
@@ -7247,7 +7265,9 @@ function findAllExact(haystack, needle) {
7247
7265
  function findWithTransforms(raw, oldString, fns) {
7248
7266
  const bufT = composeTransforms(raw, fns);
7249
7267
  const oldT = composeTransforms(oldString, fns);
7250
- if (oldT.text.length === 0) return [];
7268
+ // A needle that normalizes to bare whitespace would match an arbitrary
7269
+ // line break, so it can never identify an edit target.
7270
+ if (oldT.text.trim().length === 0) return [];
7251
7271
  const starts = findAllExact(bufT.text, oldT.text);
7252
7272
  return starts.map((s)=>({
7253
7273
  start: bufT.toOriginal[s],
@@ -11675,6 +11695,7 @@ const PRICE_LINE_RE = new RegExp([
11675
11695
  String.raw`\$\s*\d+(?:\.\d+)?\s*(?:/|per)\s*(?:image|minute|request)`
11676
11696
  ].join("|"), "gi");
11677
11697
  const GOOGLE_RESULT_RE = /href="\/url\?q=([^"&]+)[^"]*"/gi;
11698
+ const GOOGLE_OWN_HOST_RE = /^(?:www|accounts|support|maps|policies|webcache)?\.?google\.[a-z.]+$/i;
11678
11699
  function toCanonicalProvider(id) {
11679
11700
  const lower = id.toLowerCase();
11680
11701
  if (lower.includes("google") || lower.includes("gemini")) return "google";
@@ -11706,7 +11727,8 @@ async function refreshPricingSnapshot(opts) {
11706
11727
  const existing = await readPricingSnapshot();
11707
11728
  if (!force && existing) {
11708
11729
  const ageDays = (Date.now() - Date.parse(existing.generated_at)) / (24 * 60 * 60 * 1000);
11709
- if (Number.isFinite(ageDays) && ageDays < ttlDays) {
11730
+ // A future generated_at (clock skew, copied snapshot) must not pin the cache as fresh.
11731
+ if (Number.isFinite(ageDays) && ageDays >= 0 && ageDays < ttlDays) {
11710
11732
  return {
11711
11733
  refreshed: false,
11712
11734
  reason: "fresh",
@@ -11843,6 +11865,8 @@ function parseGoogleResultLinks(html) {
11843
11865
  const out = [];
11844
11866
  const seen = new Set();
11845
11867
  let m;
11868
+ // Shared /g regex: an early break leaves lastIndex mid-string for the next page.
11869
+ GOOGLE_RESULT_RE.lastIndex = 0;
11846
11870
  while((m = GOOGLE_RESULT_RE.exec(html)) !== null){
11847
11871
  const raw = m[1] ?? "";
11848
11872
  let url = "";
@@ -11852,7 +11876,11 @@ function parseGoogleResultLinks(html) {
11852
11876
  continue;
11853
11877
  }
11854
11878
  if (!/^https?:\/\//i.test(url)) continue;
11855
- if (/\/google\./i.test(url)) continue;
11879
+ try {
11880
+ if (GOOGLE_OWN_HOST_RE.test(new URL(url).hostname)) continue;
11881
+ } catch {
11882
+ continue;
11883
+ }
11856
11884
  if (seen.has(url)) continue;
11857
11885
  seen.add(url);
11858
11886
  out.push(url);
@@ -13511,58 +13539,79 @@ function containsText(source, needle, ignoreCase) {
13511
13539
  if (!ignoreCase) return source.includes(needle);
13512
13540
  return source.toLowerCase().includes(needle.toLowerCase());
13513
13541
  }
13514
- function dedupeLines(source, opts) {
13515
- const lines = source.split(/\r?\n/);
13516
- const keyed = lines.map((line)=>{
13517
- const normalized = opts.trim ? line.trim() : line;
13518
- return opts.ignoreCase ? normalized.toLowerCase() : normalized;
13519
- });
13520
- const keep = new Set();
13521
- const visit = opts.keep === "first" ? [
13522
- ...keyed.keys()
13523
- ] : [
13524
- ...keyed.keys()
13542
+ const FENCE_RE = /^\s{0,3}(`{3,}|~{3,})/;
13543
+ const HORIZONTAL_RULE_RE = /^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/;
13544
+ /**
13545
+ * Drop duplicate segments from `parts`, which alternates content (even
13546
+ * indexes) and the original separator that follows it (odd indexes).
13547
+ * Separators are preserved verbatim, so only removed segments change the
13548
+ * text. `keyOf` returns null for a segment that must always be kept.
13549
+ */ function dedupeSegments(parts, keyOf, keep) {
13550
+ const contentIdx = parts.map((_, i)=>i).filter((i)=>i % 2 === 0);
13551
+ const visit = keep === "first" ? contentIdx : [
13552
+ ...contentIdx
13525
13553
  ].reverse();
13526
13554
  const seen = new Set();
13555
+ const dropped = new Set();
13527
13556
  for (const idx of visit){
13528
- const key = keyed[idx];
13529
- if (opts.skipBlank && key.length === 0) {
13530
- keep.add(idx);
13531
- continue;
13532
- }
13533
- if (seen.has(key)) continue;
13534
- seen.add(key);
13535
- keep.add(idx);
13557
+ const key = keyOf(idx);
13558
+ if (key === null) continue;
13559
+ if (seen.has(key)) dropped.add(idx);
13560
+ else seen.add(key);
13561
+ }
13562
+ if (dropped.size === 0) return {
13563
+ text: parts.join(""),
13564
+ removed: 0
13565
+ };
13566
+ let text = "";
13567
+ let emitted = false;
13568
+ for (const idx of contentIdx){
13569
+ if (dropped.has(idx)) continue;
13570
+ if (emitted && idx > 0) text += parts[idx - 1];
13571
+ text += parts[idx];
13572
+ emitted = true;
13536
13573
  }
13537
- const next = lines.filter((_, idx)=>keep.has(idx));
13538
13574
  return {
13539
- text: next.join("\n"),
13540
- removed: lines.length - next.length
13575
+ text,
13576
+ removed: dropped.size
13541
13577
  };
13542
13578
  }
13579
+ function dedupeLines(source, opts) {
13580
+ const parts = source.split(/(\r?\n)/);
13581
+ // Fence markers, fenced code and horizontal rules repeat by design; a
13582
+ // closing ``` is always a "duplicate" of the opening one.
13583
+ const protectedIdx = new Set();
13584
+ let fence = null;
13585
+ for(let i = 0; i < parts.length; i += 2){
13586
+ const marker = FENCE_RE.exec(parts[i])?.[1];
13587
+ if (fence !== null) {
13588
+ protectedIdx.add(i);
13589
+ if (marker && marker[0] === fence[0] && marker.length >= fence.length && /^\s*[`~]+\s*$/.test(parts[i])) fence = null;
13590
+ } else if (marker) {
13591
+ protectedIdx.add(i);
13592
+ fence = marker;
13593
+ } else if (HORIZONTAL_RULE_RE.test(parts[i])) {
13594
+ protectedIdx.add(i);
13595
+ }
13596
+ }
13597
+ return dedupeSegments(parts, (i)=>{
13598
+ if (protectedIdx.has(i)) return null;
13599
+ const normalized = opts.trim ? parts[i].trim() : parts[i];
13600
+ if (opts.skipBlank && normalized.length === 0) return null;
13601
+ return opts.ignoreCase ? normalized.toLowerCase() : normalized;
13602
+ }, opts.keep);
13603
+ }
13543
13604
  function dedupeParagraphs(source, opts) {
13544
- const paragraphs = source.split(/\n\s*\n/).map((p)=>p.trim()).filter((p)=>p.length > 0);
13545
- const keys = paragraphs.map((p)=>{
13546
- const base = opts.trim ? p.trim() : p;
13605
+ const tail = /\s*$/.exec(source)?.[0] ?? "";
13606
+ const parts = source.slice(0, source.length - tail.length).split(/(\r?\n(?:[ \t]*\r?\n)+)/);
13607
+ const out = dedupeSegments(parts, (i)=>{
13608
+ if (parts[i].trim().length === 0) return null;
13609
+ const base = opts.trim ? parts[i].trim() : parts[i];
13547
13610
  return opts.ignoreCase ? base.toLowerCase() : base;
13548
- });
13549
- const keep = new Set();
13550
- const visit = opts.keep === "first" ? [
13551
- ...keys.keys()
13552
- ] : [
13553
- ...keys.keys()
13554
- ].reverse();
13555
- const seen = new Set();
13556
- for (const idx of visit){
13557
- const key = keys[idx];
13558
- if (seen.has(key)) continue;
13559
- seen.add(key);
13560
- keep.add(idx);
13561
- }
13562
- const next = paragraphs.filter((_, idx)=>keep.has(idx));
13611
+ }, opts.keep);
13563
13612
  return {
13564
- text: next.join("\n\n"),
13565
- removed: paragraphs.length - next.length
13613
+ text: out.text + tail,
13614
+ removed: out.removed
13566
13615
  };
13567
13616
  }
13568
13617
 
@@ -14363,7 +14412,7 @@ function version_getCurrentVersion() {
14363
14412
  }
14364
14413
 
14365
14414
  ;// ./docs/adoption/changesets.json
14366
- const changesets_namespaceObject = /*#__PURE__*/JSON.parse('{"schema_version":1,"changesets":[{"version":"2.0.1","previous_version":"2.0.0","date":"2026-10-11","hints":[],"commits":["chore(release): bump to 2.0.1","fix(npm): remove oversized runtime assets"],"prompts":[],"tools":[]},{"version":"2.0.2","previous_version":"2.0.1","date":"2026-10-11","hints":[],"commits":["chore(release): bump to 2.0.2","fix(npm): exclude nested server source maps"],"prompts":[],"tools":[]},{"version":"2.0.3","previous_version":"2.0.2","date":"2026-10-12","hints":[],"commits":["chore(release): bump to 2.0.3","fix(npm): remove maps before npm packing"],"prompts":[],"tools":[]},{"version":"2.0.4","previous_version":"2.0.3","date":"2026-10-12","hints":[],"commits":["chore(release): bump to 2.0.4","fix(npm): use installed onnx runtime dependencies"],"prompts":[],"tools":[]},{"version":"2.0.5","previous_version":"2.0.4","date":"2026-10-13","hints":[],"commits":["chore(release): bump to 2.0.5","fix(providers): detect streams that end without events","fix(http): fix gzip response decoding","docs(site): update guides, theme, and architecture diagrams","refactor(bridges): add capability-driven bridge modules","fix(security): update vulnerable media parser","refactor(modules): split built-in provider and package registries"],"prompts":[],"tools":[]},{"version":"2.1.0","previous_version":"2.0.5","date":"2026-10-14","hints":[],"commits":["chore(release): bump to 2.1.0","fix(release): add model download retries and cache","fix(chat): render grouped summary topic previews","fix(workflows): validate legacy checklist items","docs(pricing): document aggregator data sources","feat(core): add verified workflows, pricing signals, and summaries"],"prompts":[{"id":"static.agent.tool-usage-sop","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"removed_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"omitted_lines":0},{"id":"system.Tool usage SOP","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"removed_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"omitted_lines":0}],"tools":[{"name":"refresh_model_pricing","change":"changed","description_changed":true,"description":"Refresh provider pricing pages and separate OpenRouter/LiteLLM reference feeds, then recalculate dashboard estimates using provider-page rates only. Returns rate changes, source health, confidence, and current-versus-recorded dashboard totals. These are estimates, not invoice amounts.","previous_description":"Refresh the configured official LLM pricing sources and recalculate dashboard estimates using the latest rates. Returns rate changes, source health, confidence, and current-versus-recorded dashboard totals. These are estimates, not invoice amounts.","params_added":[],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"workflow_progress","change":"changed","description_changed":true,"description":"Report structured progress for a substantial multi-step task. Use only when the task has several dependent phases, likely spans turns, or needs explicit verification; skip simple tasks and work already covered by native tool progress. For agent-created workflows, send the full current items snapshot on every call and reuse the same workflow_id. Keep secrets and private data out of workflow fields. Mark items done only with observed validation evidence; use needs_attention for blockers; phase=complete requires every item done or skipped. Every item needs input, expected output, and concrete va…","previous_description":"Report structured progress for a substantial multi-step task. Use only when the task has several dependent phases, likely spans turns, or needs explicit verification; skip simple tasks and work already covered by native tool progress. For agent-created workflows, send the full current items snapshot on every call and reuse the same workflow_id. Keep secrets and private data out of workflow fields. Mark items done only after verifying the action; use needs_attention for blockers; phase=complete requires every item done or skipped. The system-owned version_adoption workflow has its own durable …","params_added":["validation_evidence"],"params_removed":[],"params_changed":["items"],"required_added":[],"required_removed":[]}]},{"version":"2.2.0","previous_version":"2.1.0","date":"2026-10-15","hints":[],"commits":["chore(release): bump to 2.2.0","feat(embeddings): add on-demand local model download","fix(build): exclude foreign runtimes from standalone bundles"],"prompts":[],"tools":[]},{"version":"2.2.1","previous_version":"2.2.0","date":"2026-10-15","hints":[],"commits":["chore(release): bump to 2.2.1","fix(api): skip browser http cache for client requests","feat(models): add model enable toggle and atomic default","fix(github-copilot): remove retired pat route"],"prompts":[],"tools":[]},{"version":"2.3.0","previous_version":"2.2.1","date":"2026-10-16","hints":["If an agent\'s instructions or skills tell it to leave fact capture to compaction or to avoid `memory_upsert`, remove that guidance, since agents now save facts during the turn.","If a scheduled task or watcher prompt asks the agent to write memory facts, keep that in the agent\'s own turn; automation channel summaries no longer extract facts."],"commits":["chore(repo): remove dead exports and the unused deepseek package","fix(chat): restore thread activity and bound compaction per thread","fix(chat): render automation recaps in the warm summary card","fix(chat): restore the compacting state after switching agents","fix(chat): allow compacting bridge-only threads, report via toasts","fix(scripts): align make targets and install the current working tree","feat(agents): add topic pipeline to automation channel summaries","feat(agents): add validated topic pipeline for warm compaction","feat(agents): add proactive, validated memory saving for agents","fix(chat): group older summaries into collapsible topic rows","feat(agents): add auto-retry for failed workflow step contracts","feat(chat): add per-step retry to workflow checklists","feat(lifecycle): add release changesets for version adoption","fix(chat): keep boundary locate from snapping to bottom"],"prompts":[{"id":"static.agent.compaction-group","source":"lib/agents/compaction-pipeline.ts","change":"added","added_lines":[],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.compaction-group-automation","source":"lib/agents/compaction-pipeline.ts","change":"added","added_lines":[],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.compaction-topic-recap","source":"lib/agents/compaction-pipeline.ts","change":"added","added_lines":[],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.compaction-topic-recap-automation","source":"lib/agents/compaction-pipeline.ts","change":"added","added_lines":[],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.conversation-summary","source":"lib/agents/conversation-summary.ts","change":"changed","added_lines":["A topic groups every consecutive message about one subject and must","span at least two messages (a request and its reply); fold a stray","single message into the adjacent topic instead of giving it its own."],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.tool-usage-sop","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"removed_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"omitted_lines":0},{"id":"system.Memory & recall","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["You have long-term memory across sessions, shared with every other Jarela agent. A record you save is available to other agents immediately. Background compaction also extracts facts later, but it runs late and can miss details, so it is a backstop, not your save step.","Proactive save procedure — run it whenever information fits the active memory policy, without waiting to be asked:","1. Trigger: the user states a preference, a fact about themselves or their work, a decision, a constraint, or a correction to something saved; or a tool result verifies a durable fact. Routine status, one-off chatter, secrets, credentials, and raw transcripts never qualify.","2. Check: memory_search (or memory_read for a known key) for an existing record on the same subject. Update that stable key rather than creating a duplicate or contradictory copy.","3. Write: call memory_upsert in the same turn, before your final reply, with namespace=\'facts\', a stable descriptive key, standalone content, concise tags, confidence=\'explicit\' for user-stated information (\'verified\' when tool-backed, \'inferred\' only when useful and reversible), observed_at when t…","4. Validate: the memory_upsert result must contain ok:true and the key you sent. If it failed, correct the arguments and retry once; if it still fails, tell the user it was not saved.","5. Cite: end your reply with one short line naming each saved key, for example \'Saved to memory: facts/answer-format-preference\'. Omit the line when nothing was saved."],"removed_lines":["You have long-term memory across sessions.","- Use memory_upsert proactively for structured records. Use namespace=\'facts\', concise tags, confidence=\'explicit\' for user-stated information, observed_at when the information was true, and expires_at for temporary facts. Keep memory_write only for legacy/free-form notes."],"omitted_lines":0},{"id":"system.Tool usage SOP","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"removed_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"omitted_lines":0}],"tools":[{"name":"memory_upsert","change":"changed","description_changed":true,"description":"Store a durable, structured memory record for proactive semantic recall. Use namespace=\'facts\' for information that should surface in future conversations. Store explicit user preferences, verified facts, decisions, constraints, and reusable project context; do not store transient chat details or secrets. Use an expiry for temporary facts, and status=\'archived\' to soft-retire a record instead of deleting it. Save proactively in the same turn the information appears so other agents can use it immediately; do not defer to background compaction.","previous_description":"Store a durable, structured memory record for proactive semantic recall. Use namespace=\'facts\' for information that should surface in future conversations. Store explicit user preferences, verified facts, decisions, constraints, and reusable project context; do not store transient chat details or secrets. Use an expiry for temporary facts, and status=\'archived\' to soft-retire a record instead of deleting it.","params_added":[],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"workflow_progress","change":"changed","description_changed":true,"description":"Report structured progress for a substantial multi-step task. Use only when the task has several dependent phases, likely spans turns, or needs explicit verification; skip simple tasks and work already covered by native tool progress. For agent-created workflows, send the full current items snapshot on every call and reuse the same workflow_id. Keep secrets and private data out of workflow fields. Mark items done only with observed validation evidence; use needs_attention for blockers; phase=complete requires every item done or skipped. A rejected update, or a step left checking or needs_atte…","previous_description":"Report structured progress for a substantial multi-step task. Use only when the task has several dependent phases, likely spans turns, or needs explicit verification; skip simple tasks and work already covered by native tool progress. For agent-created workflows, send the full current items snapshot on every call and reuse the same workflow_id. Keep secrets and private data out of workflow fields. Mark items done only with observed validation evidence; use needs_attention for blockers; phase=complete requires every item done or skipped. Every item needs input, expected output, and concrete va…","params_added":["needs_user"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]}]}]}');
14415
+ const changesets_namespaceObject = /*#__PURE__*/JSON.parse('{"schema_version":1,"changesets":[{"version":"2.0.1","previous_version":"2.0.0","date":"2026-10-11","hints":[],"commits":["chore(release): bump to 2.0.1","fix(npm): remove oversized runtime assets"],"prompts":[],"tools":[]},{"version":"2.0.2","previous_version":"2.0.1","date":"2026-10-11","hints":[],"commits":["chore(release): bump to 2.0.2","fix(npm): exclude nested server source maps"],"prompts":[],"tools":[]},{"version":"2.0.3","previous_version":"2.0.2","date":"2026-10-12","hints":[],"commits":["chore(release): bump to 2.0.3","fix(npm): remove maps before npm packing"],"prompts":[],"tools":[]},{"version":"2.0.4","previous_version":"2.0.3","date":"2026-10-12","hints":[],"commits":["chore(release): bump to 2.0.4","fix(npm): use installed onnx runtime dependencies"],"prompts":[],"tools":[]},{"version":"2.0.5","previous_version":"2.0.4","date":"2026-10-13","hints":[],"commits":["chore(release): bump to 2.0.5","fix(providers): detect streams that end without events","fix(http): fix gzip response decoding","docs(site): update guides, theme, and architecture diagrams","refactor(bridges): add capability-driven bridge modules","fix(security): update vulnerable media parser","refactor(modules): split built-in provider and package registries"],"prompts":[],"tools":[]},{"version":"2.1.0","previous_version":"2.0.5","date":"2026-10-14","hints":[],"commits":["chore(release): bump to 2.1.0","fix(release): add model download retries and cache","fix(chat): render grouped summary topic previews","fix(workflows): validate legacy checklist items","docs(pricing): document aggregator data sources","feat(core): add verified workflows, pricing signals, and summaries"],"prompts":[{"id":"static.agent.tool-usage-sop","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"removed_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"omitted_lines":0},{"id":"system.Tool usage SOP","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"removed_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"omitted_lines":0}],"tools":[{"name":"refresh_model_pricing","change":"changed","description_changed":true,"description":"Refresh provider pricing pages and separate OpenRouter/LiteLLM reference feeds, then recalculate dashboard estimates using provider-page rates only. Returns rate changes, source health, confidence, and current-versus-recorded dashboard totals. These are estimates, not invoice amounts.","previous_description":"Refresh the configured official LLM pricing sources and recalculate dashboard estimates using the latest rates. Returns rate changes, source health, confidence, and current-versus-recorded dashboard totals. These are estimates, not invoice amounts.","params_added":[],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"workflow_progress","change":"changed","description_changed":true,"description":"Report structured progress for a substantial multi-step task. Use only when the task has several dependent phases, likely spans turns, or needs explicit verification; skip simple tasks and work already covered by native tool progress. For agent-created workflows, send the full current items snapshot on every call and reuse the same workflow_id. Keep secrets and private data out of workflow fields. Mark items done only with observed validation evidence; use needs_attention for blockers; phase=complete requires every item done or skipped. Every item needs input, expected output, and concrete va…","previous_description":"Report structured progress for a substantial multi-step task. Use only when the task has several dependent phases, likely spans turns, or needs explicit verification; skip simple tasks and work already covered by native tool progress. For agent-created workflows, send the full current items snapshot on every call and reuse the same workflow_id. Keep secrets and private data out of workflow fields. Mark items done only after verifying the action; use needs_attention for blockers; phase=complete requires every item done or skipped. The system-owned version_adoption workflow has its own durable …","params_added":["validation_evidence"],"params_removed":[],"params_changed":["items"],"required_added":[],"required_removed":[]}]},{"version":"2.2.0","previous_version":"2.1.0","date":"2026-10-15","hints":[],"commits":["chore(release): bump to 2.2.0","feat(embeddings): add on-demand local model download","fix(build): exclude foreign runtimes from standalone bundles"],"prompts":[],"tools":[]},{"version":"2.2.1","previous_version":"2.2.0","date":"2026-10-15","hints":[],"commits":["chore(release): bump to 2.2.1","fix(api): skip browser http cache for client requests","feat(models): add model enable toggle and atomic default","fix(github-copilot): remove retired pat route"],"prompts":[],"tools":[]},{"version":"2.3.0","previous_version":"2.2.1","date":"2026-10-16","hints":["If an agent\'s instructions or skills tell it to leave fact capture to compaction or to avoid `memory_upsert`, remove that guidance, since agents now save facts during the turn.","If a scheduled task or watcher prompt asks the agent to write memory facts, keep that in the agent\'s own turn; automation channel summaries no longer extract facts."],"commits":["chore(repo): remove dead exports and the unused deepseek package","fix(chat): restore thread activity and bound compaction per thread","fix(chat): render automation recaps in the warm summary card","fix(chat): restore the compacting state after switching agents","fix(chat): allow compacting bridge-only threads, report via toasts","fix(scripts): align make targets and install the current working tree","feat(agents): add topic pipeline to automation channel summaries","feat(agents): add validated topic pipeline for warm compaction","feat(agents): add proactive, validated memory saving for agents","fix(chat): group older summaries into collapsible topic rows","feat(agents): add auto-retry for failed workflow step contracts","feat(chat): add per-step retry to workflow checklists","feat(lifecycle): add release changesets for version adoption","fix(chat): keep boundary locate from snapping to bottom"],"prompts":[{"id":"static.agent.compaction-group","source":"lib/agents/compaction-pipeline.ts","change":"added","added_lines":[],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.compaction-group-automation","source":"lib/agents/compaction-pipeline.ts","change":"added","added_lines":[],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.compaction-topic-recap","source":"lib/agents/compaction-pipeline.ts","change":"added","added_lines":[],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.compaction-topic-recap-automation","source":"lib/agents/compaction-pipeline.ts","change":"added","added_lines":[],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.conversation-summary","source":"lib/agents/conversation-summary.ts","change":"changed","added_lines":["A topic groups every consecutive message about one subject and must","span at least two messages (a request and its reply); fold a stray","single message into the adjacent topic instead of giving it its own."],"removed_lines":[],"omitted_lines":0},{"id":"static.agent.tool-usage-sop","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"removed_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"omitted_lines":0},{"id":"system.Memory & recall","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["You have long-term memory across sessions, shared with every other Jarela agent. A record you save is available to other agents immediately. Background compaction also extracts facts later, but it runs late and can miss details, so it is a backstop, not your save step.","Proactive save procedure — run it whenever information fits the active memory policy, without waiting to be asked:","1. Trigger: the user states a preference, a fact about themselves or their work, a decision, a constraint, or a correction to something saved; or a tool result verifies a durable fact. Routine status, one-off chatter, secrets, credentials, and raw transcripts never qualify.","2. Check: memory_search (or memory_read for a known key) for an existing record on the same subject. Update that stable key rather than creating a duplicate or contradictory copy.","3. Write: call memory_upsert in the same turn, before your final reply, with namespace=\'facts\', a stable descriptive key, standalone content, concise tags, confidence=\'explicit\' for user-stated information (\'verified\' when tool-backed, \'inferred\' only when useful and reversible), observed_at when t…","4. Validate: the memory_upsert result must contain ok:true and the key you sent. If it failed, correct the arguments and retry once; if it still fails, tell the user it was not saved.","5. Cite: end your reply with one short line naming each saved key, for example \'Saved to memory: facts/answer-format-preference\'. Omit the line when nothing was saved."],"removed_lines":["You have long-term memory across sessions.","- Use memory_upsert proactively for structured records. Use namespace=\'facts\', concise tags, confidence=\'explicit\' for user-stated information, observed_at when the information was true, and expires_at for temporary facts. Keep memory_write only for legacy/free-form notes."],"omitted_lines":0},{"id":"system.Tool usage SOP","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"removed_lines":["0. Classify the request. Informational answers may be direct; read/retrieve requests need a read tool; write/change requests need a state-changing tool; destructive requests also need the required confirmation. Use only these operation labels when describing work: read, write, execute, send, schedu…"],"omitted_lines":0}],"tools":[{"name":"memory_upsert","change":"changed","description_changed":true,"description":"Store a durable, structured memory record for proactive semantic recall. Use namespace=\'facts\' for information that should surface in future conversations. Store explicit user preferences, verified facts, decisions, constraints, and reusable project context; do not store transient chat details or secrets. Use an expiry for temporary facts, and status=\'archived\' to soft-retire a record instead of deleting it. Save proactively in the same turn the information appears so other agents can use it immediately; do not defer to background compaction.","previous_description":"Store a durable, structured memory record for proactive semantic recall. Use namespace=\'facts\' for information that should surface in future conversations. Store explicit user preferences, verified facts, decisions, constraints, and reusable project context; do not store transient chat details or secrets. Use an expiry for temporary facts, and status=\'archived\' to soft-retire a record instead of deleting it.","params_added":[],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"workflow_progress","change":"changed","description_changed":true,"description":"Report structured progress for a substantial multi-step task. Use only when the task has several dependent phases, likely spans turns, or needs explicit verification; skip simple tasks and work already covered by native tool progress. For agent-created workflows, send the full current items snapshot on every call and reuse the same workflow_id. Keep secrets and private data out of workflow fields. Mark items done only with observed validation evidence; use needs_attention for blockers; phase=complete requires every item done or skipped. A rejected update, or a step left checking or needs_atte…","previous_description":"Report structured progress for a substantial multi-step task. Use only when the task has several dependent phases, likely spans turns, or needs explicit verification; skip simple tasks and work already covered by native tool progress. For agent-created workflows, send the full current items snapshot on every call and reuse the same workflow_id. Keep secrets and private data out of workflow fields. Mark items done only with observed validation evidence; use needs_attention for blockers; phase=complete requires every item done or skipped. Every item needs input, expected output, and concrete va…","params_added":["needs_user"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]}]},{"version":"2.4.0","previous_version":"2.3.0","date":"2026-10-16","hints":["If an agent\'s instructions or skills tell it not to include links or URLs in replies, decide whether it should now cite sources with the provided `cite_as` and chat links.","If you listed `localhost` or `127.0.0.1` in the proxy bypass list only to reach local services, you can remove them; loopback is always direct now."],"commits":["fix(core): keep proxy loopback direct, unicode fact keys, agent tuning","fix(agents): avoid repeated compaction from bridge rows and topic pins","fix(core): keep per-chat backlog, permalink refs and code fences","fix(core): fix edge cases in edits, paging, bridges and pricing","feat(integrations): add source permalinks to mail, calendar, chats"],"prompts":[{"id":"static.agent.tool-usage-sop","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["10. Link the source. When your reply refers to a specific email, calendar event, or chat that a tool result or bridge message gave you, link it: paste the item\'s cite_as value verbatim when present, otherwise use its web_link, html_link, or chat_link URL as a markdown link. Use only links you were …"],"removed_lines":[],"omitted_lines":0},{"id":"system.Delivery channel","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["WhatsApp does not render markdown links: write any link as the bare URL, even where a cite_as value is given."],"removed_lines":[],"omitted_lines":0},{"id":"system.Tool usage SOP","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["10. Link the source. When your reply refers to a specific email, calendar event, or chat that a tool result or bridge message gave you, link it: paste the item\'s cite_as value verbatim when present, otherwise use its web_link, html_link, or chat_link URL as a markdown link. Use only links you were …"],"removed_lines":[],"omitted_lines":0}],"tools":[]}]}');
14367
14416
  ;// ./lib/lifecycle/changesets.ts
14368
14417
  /**
14369
14418
  * Bundled release changesets (docs/adoption/changesets.json), generated at
@@ -15451,6 +15500,8 @@ const compactContextTool = (0,tools/* tool */.z6)(async (_input, config)=>{
15451
15500
  var default_packages = __webpack_require__(64611);
15452
15501
  // EXTERNAL MODULE: ./lib/tools/communications/gmail.ts
15453
15502
  var gmail = __webpack_require__(71941);
15503
+ // EXTERNAL MODULE: ./lib/utils/source-link.ts
15504
+ var source_link = __webpack_require__(76385);
15454
15505
  // EXTERNAL MODULE: ./lib/integrations/gmail-oauth.ts
15455
15506
  var gmail_oauth = __webpack_require__(7721);
15456
15507
  ;// ./lib/tools/communications/calendar.ts
@@ -15481,6 +15532,7 @@ var gmail_oauth = __webpack_require__(7721);
15481
15532
 
15482
15533
 
15483
15534
 
15535
+
15484
15536
  const CALENDAR_BASE = "https://www.googleapis.com/calendar/v3";
15485
15537
  const calendarFetch = (auth, path, init)=>(0,gmail_oauth/* googleFetch */.sI)(auth, "Calendar", CALENDAR_BASE, path, init);
15486
15538
  const RFC3339_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
@@ -15503,7 +15555,8 @@ function slimEvent(e) {
15503
15555
  response: a.responseStatus ?? null
15504
15556
  })),
15505
15557
  meet_link: e.hangoutLink ?? null,
15506
- html_link: e.htmlLink ?? null
15558
+ html_link: e.htmlLink ?? null,
15559
+ cite_as: (0,source_link/* citeAs */.w)(e.summary, e.htmlLink, "Calendar event")
15507
15560
  };
15508
15561
  }
15509
15562
  // ── Tools ───────────────────────────────────────────────────────────────────
@@ -15785,6 +15838,7 @@ var microsoft_oauth = __webpack_require__(46291);
15785
15838
 
15786
15839
 
15787
15840
 
15841
+
15788
15842
  function outlook_calendar_slimEvent(e) {
15789
15843
  return {
15790
15844
  id: e.id,
@@ -15803,7 +15857,8 @@ function outlook_calendar_slimEvent(e) {
15803
15857
  is_cancelled: e.isCancelled === true,
15804
15858
  show_as: e.showAs ?? null,
15805
15859
  teams_link: e.onlineMeeting?.joinUrl ?? null,
15806
- web_link: e.webLink ?? null
15860
+ web_link: e.webLink ?? null,
15861
+ cite_as: (0,source_link/* citeAs */.w)(e.subject, e.webLink, "Outlook event")
15807
15862
  };
15808
15863
  }
15809
15864
  // ── Tools ───────────────────────────────────────────────────────────────────
@@ -21782,12 +21837,17 @@ function upsertAgentConfigTransaction(input) {
21782
21837
  const db = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .L)();
21783
21838
  const existing = getAgentConfig(input.id);
21784
21839
  const created_at = existing?.created_at ?? t;
21785
- const mbti = input.adaptive_mbti ?? toMbti(existing?.adaptive_mbti) ?? "INTJ";
21840
+ const existingMbti = toMbti(existing?.adaptive_mbti);
21841
+ const mbti = input.adaptive_mbti ?? existingMbti ?? "INTJ";
21786
21842
  const preset = _lib_agents_adaptive_persona_presets__WEBPACK_IMPORTED_MODULE_2__/* .MBTI_PRESETS */ .e[mbti];
21787
- const strength = input.adaptive_persona_strength ?? preset.strength;
21788
- const empathy = input.adaptive_empathy ?? preset.empathy;
21789
- const expressiveness = input.adaptive_expressiveness ?? preset.expressiveness;
21790
- const verbosity = input.adaptive_verbosity ?? preset.verbosity;
21843
+ // Omitted sliders keep their tuned values; they only fall back to the
21844
+ // preset when the type changes or the agent is new. Otherwise every
21845
+ // partial update (approved proposals, tool edits) reset them.
21846
+ const keepTuned = existing !== null && existing !== undefined && mbti === existingMbti;
21847
+ const strength = input.adaptive_persona_strength ?? (keepTuned ? existing.adaptive_persona_strength : preset.strength);
21848
+ const empathy = input.adaptive_empathy ?? (keepTuned ? existing.adaptive_empathy : preset.empathy);
21849
+ const expressiveness = input.adaptive_expressiveness ?? (keepTuned ? existing.adaptive_expressiveness : preset.expressiveness);
21850
+ const verbosity = input.adaptive_verbosity ?? (keepTuned ? existing.adaptive_verbosity : preset.verbosity);
21791
21851
  if (input.is_default) db.prepare("UPDATE agent_configs SET is_default=0").run();
21792
21852
  // harness_id: explicit `undefined` means "keep existing"; explicit `null`
21793
21853
  // means "use the global default". Empty string is normalised to null too,
@@ -22023,23 +22083,27 @@ async function readToolResultRef(args) {
22023
22083
  }
22024
22084
  const offset = Math.max(0, Math.floor(args.offset ?? 0));
22025
22085
  const limit = Math.min(MAX_READ_BYTES, Math.max(1, Math.floor(args.limit ?? DEFAULT_PREVIEW_BYTES)));
22026
- let buf;
22027
22086
  try {
22028
22087
  const file = await node_fs__WEBPACK_IMPORTED_MODULE_0__.promises.open(abs, "r");
22029
22088
  try {
22030
22089
  const stat = await file.stat();
22031
- const readLength = Math.max(0, Math.min(limit, stat.size - offset));
22032
- buf = Buffer.alloc(readLength);
22033
- if (readLength > 0) await file.read(buf, 0, readLength, offset);
22034
- const nextOffset = offset + readLength;
22090
+ // Read a few bytes either side so the window can be moved onto UTF-8
22091
+ // character boundaries; paging must never split a multi-byte character.
22092
+ const windowStart = Math.min(Math.max(0, offset - 3), stat.size);
22093
+ const windowLength = Math.max(0, Math.min(stat.size - windowStart, offset - windowStart + limit + 3));
22094
+ const buf = Buffer.alloc(windowLength);
22095
+ if (windowLength > 0) await file.read(buf, 0, windowLength, windowStart);
22096
+ const { start, end } = utf8Window(buf, Math.min(offset, stat.size) - windowStart, limit);
22097
+ const actualOffset = windowStart + start;
22098
+ const nextOffset = windowStart + end;
22035
22099
  return {
22036
22100
  ok: true,
22037
22101
  status: "done",
22038
22102
  name: args.name,
22039
- offset,
22103
+ offset: actualOffset,
22040
22104
  limit,
22041
22105
  bytes: stat.size,
22042
- result: buf.toString("utf8"),
22106
+ result: buf.subarray(start, end).toString("utf8"),
22043
22107
  next_offset: nextOffset < stat.size ? nextOffset : null,
22044
22108
  done: nextOffset >= stat.size
22045
22109
  };
@@ -22055,6 +22119,26 @@ async function readToolResultRef(args) {
22055
22119
  };
22056
22120
  }
22057
22121
  }
22122
+ const isUtf8Continuation = (byte)=>byte !== undefined && (byte & 0xc0) === 0x80;
22123
+ // Moves `[start, start + limit)` inside `buf` onto character boundaries: the
22124
+ // start backs up to its lead byte, and the end backs up so it does not cut a
22125
+ // character — unless that character alone exceeds `limit`, in which case the
22126
+ // whole character is returned so paging always makes progress.
22127
+ function utf8Window(buf, start, limit) {
22128
+ let s = Math.max(0, Math.min(start, buf.length));
22129
+ while(s > 0 && isUtf8Continuation(buf[s]))s--;
22130
+ let e = Math.min(buf.length, s + limit);
22131
+ if (e < buf.length && isUtf8Continuation(buf[e])) {
22132
+ let lead = e;
22133
+ while(lead > s && isUtf8Continuation(buf[lead]))lead--;
22134
+ if (lead > s) e = lead;
22135
+ else while(e < buf.length && isUtf8Continuation(buf[e]))e++;
22136
+ }
22137
+ return {
22138
+ start: s,
22139
+ end: e
22140
+ };
22141
+ }
22058
22142
  function serializeToolResult(result) {
22059
22143
  if (typeof result === "string") return result;
22060
22144
  return JSON.stringify(result) ?? String(result);
@@ -22072,10 +22156,21 @@ function buildPreview(value, mimeType) {
22072
22156
  try {
22073
22157
  const parsed = JSON.parse(value);
22074
22158
  if (Array.isArray(parsed)) {
22075
- const preview = parsed.slice(0, JSON_ARRAY_PREVIEW_ITEMS);
22159
+ // Cap by bytes as well as items: a few huge rows must not smuggle the
22160
+ // whole result back inline.
22161
+ for(let n = Math.min(parsed.length, JSON_ARRAY_PREVIEW_ITEMS); n > 0; n--){
22162
+ const preview = parsed.slice(0, n);
22163
+ const bytes = Buffer.byteLength(JSON.stringify(preview));
22164
+ if (bytes <= DEFAULT_PREVIEW_BYTES) return {
22165
+ value: preview,
22166
+ bytes,
22167
+ total_count: parsed.length
22168
+ };
22169
+ }
22170
+ const text = takeUtf8Prefix(value, DEFAULT_PREVIEW_BYTES);
22076
22171
  return {
22077
- value: preview,
22078
- bytes: Buffer.byteLength(JSON.stringify(preview)),
22172
+ value: text,
22173
+ bytes: Buffer.byteLength(text),
22079
22174
  total_count: parsed.length
22080
22175
  };
22081
22176
  }
@@ -22098,6 +22193,8 @@ function takeUtf8Prefix(value, maxBytes) {
22098
22193
  if (Buffer.byteLength(value.slice(0, mid)) <= maxBytes) lo = mid;
22099
22194
  else hi = mid - 1;
22100
22195
  }
22196
+ const code = value.charCodeAt(lo - 1);
22197
+ if (lo > 0 && code >= 0xd800 && code <= 0xdbff) lo--;
22101
22198
  return value.slice(0, lo);
22102
22199
  }
22103
22200
  function safeToolName(name) {
@@ -23830,7 +23927,7 @@ var external_node_os_ = __webpack_require__(48161);
23830
23927
  var external_node_path_ = __webpack_require__(76760);
23831
23928
  var external_node_path_default = /*#__PURE__*/__webpack_require__.n(external_node_path_);
23832
23929
  // EXTERNAL MODULE: ./node_modules/@langchain/core/dist/tools/index.js
23833
- var tools = __webpack_require__(49585);
23930
+ var tools = __webpack_require__(71966);
23834
23931
  // EXTERNAL MODULE: ./node_modules/zod/v4/classic/schemas.js + 4 modules
23835
23932
  var schemas = __webpack_require__(17033);
23836
23933
  // EXTERNAL MODULE: ./lib/stores/integrations.ts
@@ -26018,8 +26115,21 @@ const MEMORY_WRITE_NAMESPACES = new Set([
26018
26115
  function rowsInRange(rows, topic) {
26019
26116
  return rows.filter((row)=>row.seq >= (topic.start_seq ?? Infinity) && row.seq <= (topic.end_seq ?? -Infinity));
26020
26117
  }
26118
+ // Unicode-aware so non-Latin subjects get distinct keys; an ASCII-only slug
26119
+ // collapsed them all to "" and the uplift skipped them. Subjects with no
26120
+ // letters or digits fall back to a stable hash.
26021
26121
  function slugifyForMemoryKey(subject) {
26022
- return subject.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
26122
+ const slug = subject.normalize("NFC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
26123
+ const capped = Array.from(slug).slice(0, 80).join("").replace(/-+$/, "");
26124
+ return capped || `fact-${fnv1a(subject)}`;
26125
+ }
26126
+ function fnv1a(text) {
26127
+ let hash = 0x811c9dc5;
26128
+ for(let i = 0; i < text.length; i++){
26129
+ hash ^= text.charCodeAt(i);
26130
+ hash = Math.imul(hash, 0x01000193);
26131
+ }
26132
+ return (hash >>> 0).toString(16).padStart(8, "0");
26023
26133
  }
26024
26134
  /**
26025
26135
  * Merge facts by subject (case-insensitive) and memory key. Later entries win,
@@ -26031,9 +26141,18 @@ function slugifyForMemoryKey(subject) {
26031
26141
  for (const list of lists){
26032
26142
  for (const fact of list){
26033
26143
  const subjectId = fact.subject.trim().toLowerCase();
26034
- const existingSubject = fact.key ? keyToSubject.get(fact.key) : undefined;
26035
- if (existingSubject && existingSubject !== subjectId) merged.delete(existingSubject);
26036
- merged.delete(subjectId);
26144
+ const sameKeySubject = fact.key ? keyToSubject.get(fact.key) : undefined;
26145
+ for (const replaced of [
26146
+ sameKeySubject,
26147
+ subjectId
26148
+ ]){
26149
+ if (replaced === undefined) continue;
26150
+ // Drop the replaced fact's key mapping too, so a later fact reusing
26151
+ // that key cannot delete whatever now lives under this subject.
26152
+ const previousKey = merged.get(replaced)?.key;
26153
+ if (previousKey) keyToSubject.delete(previousKey);
26154
+ merged.delete(replaced);
26155
+ }
26037
26156
  merged.set(subjectId, fact);
26038
26157
  if (fact.key) keyToSubject.set(fact.key, subjectId);
26039
26158
  }
@@ -29572,7 +29691,7 @@ function truncateBytes(s, maxBytes) {
29572
29691
  /* harmony export */ });
29573
29692
  /* unused harmony exports DEFAULT_MAX_DEADLINE_MS, getMaxDeadlineMs, __DEFAULT_DEADLINE_MS */
29574
29693
  /* harmony import */ var zod__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(17033);
29575
- /* harmony import */ var _langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(49585);
29694
+ /* harmony import */ var _langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(71966);
29576
29695
  /* harmony import */ var _async_results__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(68182);
29577
29696
  /* harmony import */ var _tool_metadata__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(48271);
29578
29697
  /* harmony import */ var _result_refs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(26481);
@@ -29862,14 +29981,15 @@ function stripWrapperFields(args) {
29862
29981
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
29863
29982
  /* harmony export */ Wz: () => (/* binding */ _resolveGmailAuth)
29864
29983
  /* harmony export */ });
29865
- /* unused harmony exports buildRawMessage, gmailSearchTool, gmailGetMessageTool, gmailListLabelsTool, gmailModifyMessageTool, gmailCreateDraftTool, gmailSendEmailTool, gmailTrashMessageTool */
29866
- /* harmony import */ var _langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(49585);
29867
- /* harmony import */ var zod__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(17033);
29984
+ /* unused harmony exports gmailWebLink, buildRawMessage, gmailSearchTool, gmailGetMessageTool, gmailListLabelsTool, gmailModifyMessageTool, gmailCreateDraftTool, gmailSendEmailTool, gmailTrashMessageTool */
29985
+ /* harmony import */ var _langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(71966);
29986
+ /* harmony import */ var zod__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(17033);
29868
29987
  /* harmony import */ var _lib_utils_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(85212);
29869
- /* harmony import */ var _lib_utils_text__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(69494);
29988
+ /* harmony import */ var _lib_utils_text__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(69494);
29870
29989
  /* harmony import */ var _packages_langchain_package__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(18932);
29871
29990
  /* harmony import */ var _lib_integrations_gmail_oauth__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7721);
29872
29991
  /* harmony import */ var _lib_utils_error__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4379);
29992
+ /* harmony import */ var _lib_utils_source_link__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(76385);
29873
29993
  /**
29874
29994
  * Native Gmail tools — direct REST API calls to gmail.googleapis.com, no MCP.
29875
29995
  *
@@ -29897,6 +30017,7 @@ function stripWrapperFields(args) {
29897
30017
 
29898
30018
 
29899
30019
 
30020
+
29900
30021
  // Exposed for the integrations test endpoint.
29901
30022
  function _resolveGmailAuth() {
29902
30023
  return (0,_lib_integrations_gmail_oauth__WEBPACK_IMPORTED_MODULE_2__/* .resolveGoogleAuth */ .wq)();
@@ -29921,6 +30042,31 @@ function resolveGmailAuthForTool() {
29921
30042
  };
29922
30043
  }
29923
30044
  }
30045
+ // ── Source links ────────────────────────────────────────────────────────────
30046
+ // Gmail's web URL scheme is undocumented. `#all/<threadId>` opens the
30047
+ // conversation from All Mail whatever its labels, and `/u/<email>/` selects
30048
+ // the signed-in account (`/u/0/` is whichever account signed in first).
30049
+ const GMAIL_API_ID = /^[0-9a-f]{8,32}$/i;
30050
+ function gmailWebLink(threadId, account) {
30051
+ if (!threadId || !GMAIL_API_ID.test(threadId)) return null;
30052
+ const slot = account ? encodeURIComponent(account) : "0";
30053
+ return `https://mail.google.com/mail/u/${slot}/#all/${threadId}`;
30054
+ }
30055
+ const accountEmailCache = new Map();
30056
+ // getProfile works with every Gmail scope; a failure only drops the account
30057
+ // from the link, so it is not retried within the process.
30058
+ function gmailAccountEmail(auth) {
30059
+ const key = auth.refresh_token.slice(0, 20);
30060
+ let pending = accountEmailCache.get(key);
30061
+ if (!pending) {
30062
+ pending = gmailFetch(auth, "/profile").then((p)=>{
30063
+ const email = p.emailAddress;
30064
+ return typeof email === "string" && email.includes("@") ? email : null;
30065
+ }).catch(()=>null);
30066
+ accountEmailCache.set(key, pending);
30067
+ }
30068
+ return pending;
30069
+ }
29924
30070
  // ── Decoders / helpers ──────────────────────────────────────────────────────
29925
30071
  // Gmail uses base64url (no padding, +/ → -_). Node's Buffer handles it directly.
29926
30072
  function decodeBase64Url(s) {
@@ -29993,6 +30139,7 @@ const gmailSearchTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/*
29993
30139
  messages: []
29994
30140
  });
29995
30141
  }
30142
+ const account = await gmailAccountEmail(auth);
29996
30143
  // Parallel metadata fetch; the API recommends batching but for ≤100 ids
29997
30144
  // parallel single calls are simpler and well within quota.
29998
30145
  const details = await Promise.all(ids.map(async (id)=>{
@@ -30001,15 +30148,19 @@ const gmailSearchTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/*
30001
30148
  id,
30002
30149
  error: m.error
30003
30150
  };
30151
+ const subject = header(m.payload?.headers, "Subject");
30152
+ const webLink = gmailWebLink(m.threadId, account);
30004
30153
  return {
30005
30154
  id: m.id,
30006
30155
  thread_id: m.threadId,
30007
30156
  from: header(m.payload?.headers, "From"),
30008
30157
  to: header(m.payload?.headers, "To"),
30009
- subject: header(m.payload?.headers, "Subject"),
30158
+ subject,
30010
30159
  date: header(m.payload?.headers, "Date"),
30011
30160
  snippet: m.snippet ?? null,
30012
- labels: m.labelIds ?? []
30161
+ labels: m.labelIds ?? [],
30162
+ web_link: webLink,
30163
+ cite_as: (0,_lib_utils_source_link__WEBPACK_IMPORTED_MODULE_5__/* .citeAs */ .w)(subject, webLink, "Gmail message")
30013
30164
  };
30014
30165
  }));
30015
30166
  return JSON.stringify({
@@ -30021,9 +30172,9 @@ const gmailSearchTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/*
30021
30172
  }, {
30022
30173
  name: "gmail_search",
30023
30174
  description: "Search the user's Gmail with Gmail query syntax. Returns message metadata (from, to, subject, " + "date, snippet, labels) plus Gmail's resultSizeEstimate and next page token so '1 result' " + "doesn't get mistaken for '1 unread email'. Examples: 'is:unread newer_than:1d', " + "'from:notifications@github.com', 'subject:invoice has:attachment', 'in:inbox -category:promotions'. " + "Use this before calling gmail_get_message — the snippet is often enough.",
30024
- schema: zod__WEBPACK_IMPORTED_MODULE_5__/* .object */ .Ikc({
30025
- query: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP().describe("Gmail query string"),
30026
- max_results: zod__WEBPACK_IMPORTED_MODULE_5__/* .number */ .aig().int().optional().describe("Max messages (default 25, max 100)")
30175
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30176
+ query: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().describe("Gmail query string"),
30177
+ max_results: zod__WEBPACK_IMPORTED_MODULE_6__/* .number */ .aig().int().optional().describe("Max messages (default 25, max 100)")
30027
30178
  })
30028
30179
  });
30029
30180
  const gmailGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ id })=>{
@@ -30032,15 +30183,19 @@ const gmailGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0_
30032
30183
  const m = await gmailFetch(auth, `/messages/${encodeURIComponent(id)}?format=full`);
30033
30184
  if (m.error) return JSON.stringify(m);
30034
30185
  const body = extractBody(m.payload);
30035
- const { text: capped, truncated } = (0,_lib_utils_text__WEBPACK_IMPORTED_MODULE_6__/* .truncateBytes */ .Q)(body, MAX_BODY_BYTES);
30186
+ const { text: capped, truncated } = (0,_lib_utils_text__WEBPACK_IMPORTED_MODULE_7__/* .truncateBytes */ .Q)(body, MAX_BODY_BYTES);
30187
+ const subject = header(m.payload?.headers, "Subject");
30188
+ const webLink = gmailWebLink(m.threadId, await gmailAccountEmail(auth));
30036
30189
  return JSON.stringify({
30037
30190
  id: m.id,
30038
30191
  thread_id: m.threadId,
30192
+ web_link: webLink,
30193
+ cite_as: (0,_lib_utils_source_link__WEBPACK_IMPORTED_MODULE_5__/* .citeAs */ .w)(subject, webLink, "Gmail message"),
30039
30194
  labels: m.labelIds ?? [],
30040
30195
  from: header(m.payload?.headers, "From"),
30041
30196
  to: header(m.payload?.headers, "To"),
30042
30197
  cc: header(m.payload?.headers, "Cc"),
30043
- subject: header(m.payload?.headers, "Subject"),
30198
+ subject,
30044
30199
  date: header(m.payload?.headers, "Date"),
30045
30200
  message_id: header(m.payload?.headers, "Message-Id") ?? header(m.payload?.headers, "Message-ID"),
30046
30201
  snippet: m.snippet ?? null,
@@ -30050,8 +30205,8 @@ const gmailGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0_
30050
30205
  }, {
30051
30206
  name: "gmail_get_message",
30052
30207
  description: "Fetch one Gmail message by id, including full headers and the decoded text body (text/plain " + "preferred, HTML stripped if only HTML is available). Body capped at 30KB with `truncated:true` " + "when cut off.",
30053
- schema: zod__WEBPACK_IMPORTED_MODULE_5__/* .object */ .Ikc({
30054
- id: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP().describe("Gmail message id (from gmail_search results)")
30208
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30209
+ id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().describe("Gmail message id (from gmail_search results)")
30055
30210
  })
30056
30211
  });
30057
30212
  const gmailListLabelsTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ()=>{
@@ -30069,7 +30224,7 @@ const gmailListLabelsTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0_
30069
30224
  }, {
30070
30225
  name: "gmail_list_labels",
30071
30226
  description: "List all Gmail labels (system + user). System label ids are uppercase (INBOX, UNREAD, " + "STARRED, IMPORTANT, SPAM, TRASH, SENT, DRAFT, CATEGORY_*). User labels have lowercase " + "alphanumeric ids like 'Label_42'. Use the id values with gmail_modify_message.",
30072
- schema: zod__WEBPACK_IMPORTED_MODULE_5__/* .object */ .Ikc({})
30227
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({})
30073
30228
  });
30074
30229
  const gmailModifyMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ id, add_labels, remove_labels })=>{
30075
30230
  const auth = resolveGmailAuthForTool();
@@ -30095,10 +30250,10 @@ const gmailModifyMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE
30095
30250
  }, {
30096
30251
  name: "gmail_modify_message",
30097
30252
  description: "Add or remove labels on a Gmail message. Common patterns: " + "archive = remove_labels=['INBOX']; mark read = remove_labels=['UNREAD']; " + "star = add_labels=['STARRED']; mark important = add_labels=['IMPORTANT']. " + "Use gmail_list_labels first if you don't know a user label id.",
30098
- schema: zod__WEBPACK_IMPORTED_MODULE_5__/* .object */ .Ikc({
30099
- id: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP().describe("Gmail message id"),
30100
- add_labels: zod__WEBPACK_IMPORTED_MODULE_5__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP()).optional().describe("Label ids to add"),
30101
- remove_labels: zod__WEBPACK_IMPORTED_MODULE_5__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP()).optional().describe("Label ids to remove")
30253
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30254
+ id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().describe("Gmail message id"),
30255
+ add_labels: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional().describe("Label ids to add"),
30256
+ remove_labels: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional().describe("Label ids to remove")
30102
30257
  })
30103
30258
  });
30104
30259
  const gmailCreateDraftTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ to, cc, bcc, subject, body, content_type, in_reply_to_id })=>{
@@ -30153,17 +30308,17 @@ const gmailCreateDraftTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0
30153
30308
  }, {
30154
30309
  name: "gmail_create_draft",
30155
30310
  description: "Create a Gmail draft. Prefer drafts unless the user explicitly asks to send immediately. " + "`content_type` defaults to 'text'; pass 'html' if `body` contains HTML markup. " + "When replying to an existing message, pass `in_reply_to_id` (the parent " + "message id from gmail_search/gmail_get_message) — the tool will set Message-Id/References/" + "threadId so the draft appears inside the right Gmail thread.",
30156
- schema: zod__WEBPACK_IMPORTED_MODULE_5__/* .object */ .Ikc({
30157
- to: zod__WEBPACK_IMPORTED_MODULE_5__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP()).describe("Recipient email addresses"),
30158
- cc: zod__WEBPACK_IMPORTED_MODULE_5__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP()).optional(),
30159
- bcc: zod__WEBPACK_IMPORTED_MODULE_5__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP()).optional(),
30160
- subject: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP(),
30161
- body: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP().max(DRAFT_BODY_MAX_CHARS).describe("Email body (max 100,000 characters)"),
30162
- content_type: zod__WEBPACK_IMPORTED_MODULE_5__/* ["enum"] */ .k5n([
30311
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30312
+ to: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).describe("Recipient email addresses"),
30313
+ cc: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional(),
30314
+ bcc: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional(),
30315
+ subject: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP(),
30316
+ body: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().max(DRAFT_BODY_MAX_CHARS).describe("Email body (max 100,000 characters)"),
30317
+ content_type: zod__WEBPACK_IMPORTED_MODULE_6__/* ["enum"] */ .k5n([
30163
30318
  "text",
30164
30319
  "html"
30165
30320
  ]).optional().describe("Body content type (default 'text')"),
30166
- in_reply_to_id: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP().optional().describe("Gmail message id to reply to (for threading)")
30321
+ in_reply_to_id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().optional().describe("Gmail message id to reply to (for threading)")
30167
30322
  })
30168
30323
  });
30169
30324
  const gmailSendEmailTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ to, cc, bcc, subject, body, content_type, in_reply_to_id })=>{
@@ -30213,17 +30368,17 @@ const gmailSendEmailTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__
30213
30368
  }, {
30214
30369
  name: "gmail_send_email",
30215
30370
  description: "Send a Gmail email immediately. Only use when the user explicitly asks to send now; " + "otherwise create a draft with gmail_create_draft. Supports text or HTML bodies via `content_type`. " + "When replying, pass `in_reply_to_id` so Gmail threads the sent message correctly.",
30216
- schema: zod__WEBPACK_IMPORTED_MODULE_5__/* .object */ .Ikc({
30217
- to: zod__WEBPACK_IMPORTED_MODULE_5__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP()).min(1).describe("Recipient email addresses"),
30218
- cc: zod__WEBPACK_IMPORTED_MODULE_5__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP()).optional(),
30219
- bcc: zod__WEBPACK_IMPORTED_MODULE_5__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP()).optional(),
30220
- subject: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP(),
30221
- body: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP().max(SEND_BODY_MAX_CHARS).describe("Email body (max 100,000 characters)"),
30222
- content_type: zod__WEBPACK_IMPORTED_MODULE_5__/* ["enum"] */ .k5n([
30371
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30372
+ to: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).min(1).describe("Recipient email addresses"),
30373
+ cc: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional(),
30374
+ bcc: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional(),
30375
+ subject: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP(),
30376
+ body: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().max(SEND_BODY_MAX_CHARS).describe("Email body (max 100,000 characters)"),
30377
+ content_type: zod__WEBPACK_IMPORTED_MODULE_6__/* ["enum"] */ .k5n([
30223
30378
  "text",
30224
30379
  "html"
30225
30380
  ]).optional().describe("Body content type (default 'text')"),
30226
- in_reply_to_id: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP().optional().describe("Gmail message id to reply to (for threading)")
30381
+ in_reply_to_id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().optional().describe("Gmail message id to reply to (for threading)")
30227
30382
  })
30228
30383
  });
30229
30384
  const gmailTrashMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ id })=>{
@@ -30241,8 +30396,8 @@ const gmailTrashMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_
30241
30396
  }, {
30242
30397
  name: "gmail_trash_message",
30243
30398
  description: "Move a Gmail message to Trash (reversible for 30 days, not permanent delete). Only call this " + "when the user has explicitly asked to trash/delete a message — never on your own initiative " + "as part of a triage flow. Prefer gmail_modify_message remove_labels=['INBOX'] to archive.",
30244
- schema: zod__WEBPACK_IMPORTED_MODULE_5__/* .object */ .Ikc({
30245
- id: zod__WEBPACK_IMPORTED_MODULE_5__/* .string */ .YjP().describe("Gmail message id")
30399
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30400
+ id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().describe("Gmail message id")
30246
30401
  })
30247
30402
  });
30248
30403
  (0,_packages_langchain_package__WEBPACK_IMPORTED_MODULE_1__/* .registerLangChainPackage */ .p)({
@@ -31422,6 +31577,51 @@ async function indexAllSources(opts) {
31422
31577
  }
31423
31578
 
31424
31579
 
31580
+ /***/ }),
31581
+
31582
+ /***/ 76385:
31583
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
31584
+
31585
+ "use strict";
31586
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
31587
+ /* harmony export */ w: () => (/* binding */ citeAs)
31588
+ /* harmony export */ });
31589
+ /* unused harmony export httpsUrlOrNull */
31590
+ /**
31591
+ * Source-link fields for tool results, so an agent can link a reply back to
31592
+ * the email, calendar event, or chat it came from without building URLs.
31593
+ *
31594
+ * `cite_as` is a ready-made markdown link the system prompt tells agents to
31595
+ * paste verbatim. Building it in code keeps the label and URL well-formed:
31596
+ * a stray `]` in a subject or `)` in a URL would otherwise break the link.
31597
+ */ const MAX_LABEL_CHARS = 80;
31598
+ /** Returns `url` when it is an absolute https URL, otherwise null. */ function httpsUrlOrNull(url) {
31599
+ if (!url) return null;
31600
+ try {
31601
+ const parsed = new URL(url);
31602
+ return parsed.protocol === "https:" ? parsed.href : null;
31603
+ } catch {
31604
+ return null;
31605
+ }
31606
+ }
31607
+ function markdownLabel(label, fallback) {
31608
+ const flat = (label ?? "").replace(/\s+/g, " ").trim();
31609
+ const capped = flat.length > MAX_LABEL_CHARS ? `${flat.slice(0, MAX_LABEL_CHARS - 1).trimEnd()}…` : flat;
31610
+ return (capped || fallback).replace(/[\\[\]]/g, (c)=>`\\${c}`);
31611
+ }
31612
+ function markdownDestination(url) {
31613
+ return url.replace(/[()\s<>]/g, (c)=>`%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
31614
+ }
31615
+ /**
31616
+ * Pre-built markdown link for `cite_as`, or null when there is no usable URL.
31617
+ * Only https URLs qualify: the chat renderer drops other schemes.
31618
+ */ function citeAs(label, url, fallbackLabel = "source") {
31619
+ const safe = httpsUrlOrNull(url);
31620
+ if (!safe) return null;
31621
+ return `[${markdownLabel(label, fallbackLabel)}](${markdownDestination(safe)})`;
31622
+ }
31623
+
31624
+
31425
31625
  /***/ }),
31426
31626
 
31427
31627
  /***/ 82703:
@@ -32138,7 +32338,7 @@ __webpack_require__.d(__webpack_exports__, {
32138
32338
  IC: () => (/* binding */ withInterruptMarker)
32139
32339
  });
32140
32340
 
32141
- // UNUSED EXPORTS: INTERRUPT_MARKER, MAX_RATE_LIMIT_RETRY_AFTER_MS, RunThreadError, appendHistoryMessage, buildFabricationNudge, buildRetryContextSummary, contentText, isHotContextNearBudget, looksLikeStall, persistThreadRunUserMessage, rateLimitRetryDelayMs, retryTextOverlapRatio, shouldEmitChunk, shouldRetryTransientError, toolCallSignature, transientRetryDelayMs
32341
+ // UNUSED EXPORTS: INTERRUPT_MARKER, MAX_RATE_LIMIT_RETRY_AFTER_MS, RunThreadError, appendHistoryMessage, buildFabricationNudge, buildRetryContextSummary, contentText, hotTurnCooldownUntil, isHotContextNearBudget, looksLikeStall, persistThreadRunUserMessage, rateLimitRetryDelayMs, retryTextOverlapRatio, shouldEmitChunk, shouldRetryTransientError, toolCallSignature, transientRetryDelayMs
32142
32342
 
32143
32343
  // EXTERNAL MODULE: ./lib/agents/action-vocabulary.ts
32144
32344
  var action_vocabulary = __webpack_require__(56033);
@@ -35867,7 +36067,8 @@ function buildSharedToolCatalogContext() {
35867
36067
  "6. Read large results by reference. A tool result with truncated=true is only a preview; do not treat it as complete. Use result_ref.name with tool_result_get and offset/limit to read only the slice you need.",
35868
36068
  "7. Verify before reporting. A read-only result does not prove a new write. Claim a new write, send, schedule, delete, or configuration change only after the matching state-changing tool returns success in this turn. Trusted runtime system signals and their matching retrieved results can substantiate earlier named operations; attribute those outcomes to their receipts, never imply a new action or repeat completed work for proof.",
35869
36069
  "8. Stop and ask. Do not retry a tool denied for agent_not_allowed, category_disabled, dropin_tool_disabled, credentials_missing or integration_unconfigured. The first three are permission decisions and the last two mean the integration is not set up \u2014 propose a configuration change or tell the user what to configure.",
35870
- "9. Completion-reporting turns are read-only. permission_reason=signal_continuation_read_only forbids state-changing tools in this turn, including through invoke_tool. Do not bypass it with shell commands or treat an old user message as renewed permission; report the completed outcome and wait for a new user turn."
36070
+ "9. Completion-reporting turns are read-only. permission_reason=signal_continuation_read_only forbids state-changing tools in this turn, including through invoke_tool. Do not bypass it with shell commands or treat an old user message as renewed permission; report the completed outcome and wait for a new user turn.",
36071
+ "10. Link the source. When your reply refers to a specific email, calendar event, or chat that a tool result or bridge message gave you, link it: paste the item's cite_as value verbatim when present, otherwise use its web_link, html_link, or chat_link URL as a markdown link. Use only links you were given; never construct, guess, or edit one. If the item has no link, name it without one."
35871
36072
  ].join("\n");
35872
36073
  }
35873
36074
  function compareToolPermissionEntries(a, b) {
@@ -35947,9 +36148,14 @@ function buildDeliveryChannelContext(channel) {
35947
36148
  return [
35948
36149
  "--- Delivery channel ---",
35949
36150
  `This message reached you over ${platform}${named}, not the regular chat UI.`,
35950
- `You DO have access to ${platform} for this conversation: any reply you produce is sent back through that channel automatically. Do not tell the user the platform is unavailable; just answer the message.`
35951
- ].join("\n");
36151
+ `You DO have access to ${platform} for this conversation: any reply you produce is sent back through that channel automatically. Do not tell the user the platform is unavailable; just answer the message.`,
36152
+ PLAIN_TEXT_DELIVERY_KINDS.has(channel.kind) ? `${platform} does not render markdown links: write any link as the bare URL, even where a cite_as value is given.` : ""
36153
+ ].filter(Boolean).join("\n");
35952
36154
  }
36155
+ const PLAIN_TEXT_DELIVERY_KINDS = new Set([
36156
+ "whatsapp",
36157
+ "sms"
36158
+ ]);
35953
36159
  function buildUserContext() {
35954
36160
  const userProfile = (0,user_profile/* getUserProfile */.VM)();
35955
36161
  const parts = [];
@@ -36033,7 +36239,7 @@ function buildSourceLinkContext(agent, manifest) {
36033
36239
  "--- Citation format ---",
36034
36240
  intro,
36035
36241
  "PRIMARY (inline links): cite by writing a standard markdown link inline: `[short label](https://example.com/path)` or `[file name](/abs/path.ts)` or `[earlier reply](#msg-<id>)`. Wrap the cited PHRASE itself as the link text when natural — `Jade has [ice skating at 16:00](?thread=…&agent=…)` reads better than a trailing `[1]`.",
36036
- "When a tool result contains a `cite_as` field (delegate_to_agent does, and so do some integrations), paste that exact value somewhere in your reply — it's a pre-built markdown link to the source. Anything you summarize from a delegate or from a web/file tool result MUST carry at least one inline link back to where it came from; a summary with zero links reads as if you made the facts up.",
36242
+ "When a tool result contains a `cite_as` field (delegate_to_agent does, and so do the email and calendar tools), paste that exact value somewhere in your reply — it's a pre-built markdown link to the source. Anything you summarize from a delegate or from a web/file tool result MUST carry at least one inline link back to where it came from; a summary with zero links reads as if you made the facts up.",
36037
36243
  "For web_search / fetch_webpage results, the URL is right there in the tool output — use it: `[headline or short label](https://...)`. For file_read results, link the absolute path: `[filename.ext](/abs/path/filename.ext)`.",
36038
36244
  "FALLBACK (declared refs block): if a source is awkward to cite inline (e.g. you summarized 4 articles in one paragraph), you MAY end your reply with a fenced block exactly like this:",
36039
36245
  "```jarela-references",
@@ -36532,17 +36738,27 @@ const SOURCE_PRODUCING_TOOLS_RESULT = new Set([
36532
36738
  "fetch_webpage",
36533
36739
  "web_fetch"
36534
36740
  ]);
36535
- // Strip query/fragment for URLs and normalize backslashes for paths so a
36536
- // citation that drops `?utm=...` or uses forward slashes on Windows still
36537
- // matches the visited entry. Also fold `~/...` to the resolved home dir
36538
- // so `~/Downloads/x` and `C:\Users\andre\Downloads\x` de-dupe.
36741
+ const TRACKING_PARAM_RE = /^(?:utm(?:_.*)?|fbclid|gclid|dclid|msclkid|mc_cid|mc_eid|igshid|ref_src)$/i;
36742
+ // Drop tracking params and in-page anchors for URLs and normalize
36743
+ // backslashes for paths so a citation that adds `?utm_source=...` or uses
36744
+ // forward slashes on Windows still matches the visited entry. Identifying
36745
+ // query params (Outlook `?ItemID=`, `watch?v=`) and hash routes (Gmail
36746
+ // `#all/<id>`) are kept verbatim: they are case-sensitive and are the only
36747
+ // thing that distinguishes two message permalinks. Also fold `~/...` to the
36748
+ // resolved home dir so `~/Downloads/x` and `C:\Users\andre\Downloads\x` de-dupe.
36539
36749
  function normalizeSource(raw) {
36540
36750
  const trimmed = raw.trim();
36541
36751
  if (!trimmed) return "";
36542
36752
  if (/^https?:\/\//i.test(trimmed)) {
36543
36753
  try {
36544
36754
  const u = new URL(trimmed);
36545
- return `${u.protocol}//${u.host}${u.pathname}`.replace(/\/+$/, "").toLowerCase();
36755
+ const base = `${u.protocol}//${u.host}${u.pathname}`.replace(/\/+$/, "").toLowerCase();
36756
+ const params = [
36757
+ ...u.searchParams
36758
+ ].filter(([key])=>!TRACKING_PARAM_RE.test(key)).sort(([a], [b])=>a < b ? -1 : a > b ? 1 : 0);
36759
+ const query = params.length > 0 ? `?${new URLSearchParams(params).toString()}` : "";
36760
+ const hashRoute = u.hash.includes("/") ? u.hash : "";
36761
+ return base + query + hashRoute;
36546
36762
  } catch {
36547
36763
  return trimmed.toLowerCase();
36548
36764
  }
@@ -36891,15 +37107,8 @@ ${sent}`;
36891
37107
  */ function parseCitationVerdict(text) {
36892
37108
  if (!text) return null;
36893
37109
  const stripped = text.replace(/```(?:json)?\s*/gi, "").replace(/```/g, "").trim();
36894
- const m = /\{[^]*"claims"[^]*\}/.exec(stripped);
36895
- if (!m) return null;
36896
- let parsed;
36897
- try {
36898
- parsed = JSON.parse(m[0]);
36899
- } catch {
36900
- return null;
36901
- }
36902
- if (!parsed || typeof parsed !== "object") return null;
37110
+ const parsed = findClaimsObject(stripped);
37111
+ if (!parsed) return null;
36903
37112
  const claimsRaw = parsed.claims;
36904
37113
  if (!Array.isArray(claimsRaw)) return null;
36905
37114
  const claims = [];
@@ -36926,6 +37135,41 @@ ${sent}`;
36926
37135
  }
36927
37136
  return claims;
36928
37137
  }
37138
+ // Models often wrap the verdict in prose that itself contains braces, so a
37139
+ // single greedy `{...}` match can start or end in the wrong place. Try each
37140
+ // `{` as a start and parse the balanced object it opens (string-aware).
37141
+ function findClaimsObject(text) {
37142
+ for(let start = text.indexOf("{"); start !== -1; start = text.indexOf("{", start + 1)){
37143
+ const end = balancedObjectEnd(text, start);
37144
+ if (end === -1) continue;
37145
+ const candidate = text.slice(start, end + 1);
37146
+ if (!candidate.includes('"claims"')) continue;
37147
+ try {
37148
+ const parsed = JSON.parse(candidate);
37149
+ if (parsed && typeof parsed === "object" && "claims" in parsed) return parsed;
37150
+ } catch {}
37151
+ }
37152
+ return null;
37153
+ }
37154
+ function balancedObjectEnd(text, start) {
37155
+ let depth = 0;
37156
+ let inString = false;
37157
+ for(let i = start; i < text.length; i++){
37158
+ const ch = text[i];
37159
+ if (inString) {
37160
+ if (ch === "\\") i++;
37161
+ else if (ch === '"') inString = false;
37162
+ } else if (ch === '"') {
37163
+ inString = true;
37164
+ } else if (ch === "{") {
37165
+ depth++;
37166
+ } else if (ch === "}") {
37167
+ depth--;
37168
+ if (depth === 0) return i;
37169
+ }
37170
+ }
37171
+ return -1;
37172
+ }
36929
37173
  function collectStringsFromPayload(payload, out) {
36930
37174
  if (!payload) return;
36931
37175
  if (typeof payload === "string") {
@@ -37089,17 +37333,31 @@ function hotTurnBoundary(threadId, agentCfg, scope, bridgeKey) {
37089
37333
  const boundary = userMessages[userMessages.length - turnLimit];
37090
37334
  return boundary?.seq ?? null;
37091
37335
  }
37092
- async function maybeAutoCompactOversizedThread(agentId, threadId, messageCount, onCompacting, preserveNewestMessages = 0) {
37336
+ // The topic-aware boundary keeps the whole active topic hot, so user turns
37337
+ // since the pin usually still exceed the limit right after a commit. Without a
37338
+ // cooldown every following turn re-runs the topic LLM pass only to land on the
37339
+ // same pin. Skip the next half-limit turns (2 rows per turn) before re-checking.
37340
+ function hotTurnCooldownUntil(messageCount, hotTurnLimit) {
37341
+ const turnLimit = hotTurnLimit ?? types/* DEFAULT_HOT_TURN_LIMIT */.u_;
37342
+ const skipTurns = Math.max(1, Math.ceil(Math.max(1, turnLimit) / 2));
37343
+ return messageCount + skipTurns * 2 + 1;
37344
+ }
37345
+ async function maybeAutoCompactOversizedThread(agentId, threadId, onCompacting, preserveNewestMessages = 0) {
37093
37346
  const cap = (0,config/* getConfig */.z)().maxThreadMessages;
37094
37347
  const keepLast = autoCompactionKeepLast(cap);
37095
37348
  const retainedMessages = keepLast + preserveNewestMessages;
37096
37349
  const triggerAt = cap + (cap - keepLast);
37097
- if (!Number.isFinite(cap) || cap <= 0 || messageCount < triggerAt) return;
37350
+ if (!Number.isFinite(cap) || cap <= 0) return;
37351
+ // Only foreground rows are ever pruned; counting bridge/watcher/scheduled
37352
+ // rows would re-trigger an LLM compaction on every turn of a busy bridge
37353
+ // agent without ever bringing the count back under the cap.
37354
+ const messageCount = (0,threads/* countForegroundThreadMessages */.Tb)(threadId);
37355
+ if (messageCount < triggerAt) return;
37098
37356
  try {
37099
37357
  // A pin that already keeps the hot tier within the retained range must not
37100
37358
  // be pulled back to the cutoff, nor re-summarised by an LLM on every turn.
37101
37359
  const pinSeq = (0,threads/* getThread */.fG)(threadId)?.hot_since_seq ?? null;
37102
- if (pinSeq !== null && (0,threads/* countThreadMessagesFromSeq */.fW)(threadId, pinSeq) <= retainedMessages) {
37360
+ if (pinSeq !== null && (0,threads/* countForegroundThreadMessages */.Tb)(threadId, pinSeq) <= retainedMessages) {
37103
37361
  (0,threads/* pruneThreadMessages */.vC)(threadId, retainedMessages, pinSeq);
37104
37362
  return;
37105
37363
  }
@@ -37138,7 +37396,6 @@ async function persistThreadRunUserMessage(req) {
37138
37396
  if (!trimmed && !req.attachments?.length) {
37139
37397
  throw new RunThreadError(400, "message required", "message_required");
37140
37398
  }
37141
- const messageCountBefore = thread.message_count;
37142
37399
  const autoBoundaryScope = req.context_profile?.history_scope ?? (req.user_category === "bridge" ? "bridge" : "foreground");
37143
37400
  const idleMsBefore = isAutoBoundaryEligibleCategory(req.user_category) ? conversationIdleMs(req.thread_id, autoBoundaryScope, req.history_bridge_key ?? undefined) : null;
37144
37401
  const spilledAttachments = req.attachments?.length ? await spillAttachments(req.attachments) : undefined;
@@ -37158,7 +37415,6 @@ async function persistThreadRunUserMessage(req) {
37158
37415
  });
37159
37416
  return {
37160
37417
  attachments: spilledAttachments,
37161
- messageCountBefore,
37162
37418
  idleMsBefore,
37163
37419
  userMessageId: userMessage.msg_id,
37164
37420
  storedContent: stored
@@ -37305,7 +37561,7 @@ function appendHistoryMessage(history, injected) {
37305
37561
  let toolPermissionMap = baseToolPermissionMap;
37306
37562
  let allowedTools = requestedAllowedTools;
37307
37563
  if (!req._skip_persist_message && isAutoBoundaryEligibleCategory(req.user_category) && !hasPendingQueuedUserMessage(req.thread_id)) {
37308
- await maybeAutoCompactOversizedThread(agentCfg.id, req.thread_id, req._message_count_before_user ?? thread.message_count, req._onCompacting, req._user_message_persisted ? 2 : 0);
37564
+ await maybeAutoCompactOversizedThread(agentCfg.id, req.thread_id, req._onCompacting, req._user_message_persisted ? 2 : 0);
37309
37565
  }
37310
37566
  // Persist the user turn (including any attachments) before the LLM stream
37311
37567
  // so reload-mid-stream still shows the prompt. The stall-retry path sets
@@ -37347,7 +37603,11 @@ function appendHistoryMessage(history, injected) {
37347
37603
  const locked = !!latestThread && latestThread.message_count < (latestThread.auto_boundary_locked_until_msg_count ?? 0);
37348
37604
  if (!locked) {
37349
37605
  const boundary = hotTurnBoundary(req.thread_id, agentCfg, autoBoundaryScope);
37350
- if (boundary !== null) (0,warm_summary_background/* kickBoundaryCompaction */.Wg)(req.thread_id, boundary);
37606
+ if (boundary !== null) {
37607
+ (0,warm_summary_background/* kickBoundaryCompaction */.Wg)(req.thread_id, boundary, {
37608
+ autoBoundaryLockedUntilMessageCount: hotTurnCooldownUntil(latestThread?.message_count ?? 0, agentCfg.hot_turn_limit)
37609
+ });
37610
+ }
37351
37611
  }
37352
37612
  }
37353
37613
  }
@@ -38814,12 +39074,11 @@ function isBasicToolCategory(category) {
38814
39074
 
38815
39075
  // Every caller of moveThreadContextBoundary represents a user-initiated
38816
39076
  // boundary change (drag the line, the manual /compact command, or the agent's
38817
- // compact_context tool acting on the user's say-so) — the
38818
- // auto-detector's own commit (lib/agents/warm-summary-background.ts
38819
- // commitBoundaryCompaction) calls setThreadContextPin directly instead, so
38820
- // it never re-arms this lock. Suppresses the hot-turn-limit compaction in
38821
- // lib/agents/run-thread.ts for the next N eligible turns so a manual
38822
- // choice doesn't get silently re-moved on the very next turn.
39077
+ // compact_context tool acting on the user's say-so). Suppresses the
39078
+ // hot-turn-limit compaction in lib/agents/run-thread.ts for the next N
39079
+ // eligible turns so a manual choice doesn't get silently re-moved on the very
39080
+ // next turn. The auto-detector arms a shorter cooldown of its own
39081
+ // (hotTurnCooldownUntil) so it doesn't re-run on every turn either.
38823
39082
  // Counted in messages (~2 per turn: one user + one assistant row) since
38824
39083
  // that's what's already on the thread row — no extra query or per-turn
38825
39084
  // write needed to check it.