@circuitwall/jarela 2.3.0 → 2.5.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 (290) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +5 -5
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +4 -4
  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 +14 -2
  13. package/.next/standalone/.next/server/app/_not-found.rsc +16 -4
  14. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -4
  15. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +16 -4
  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/dashboard/pricing-task/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/v1/documents/search/route.js +7 -0
  50. package/.next/standalone/.next/server/app/api/v1/documents/settings/route.js +7 -0
  51. package/.next/standalone/.next/server/app/api/v1/documents/sources/[id]/route.js +1 -1
  52. package/.next/standalone/.next/server/app/api/v1/documents/sources/route.js +1 -1
  53. package/.next/standalone/.next/server/app/api/v1/env-sync/allowlist/route.js +7 -0
  54. package/.next/standalone/.next/server/app/api/v1/env-sync/route.js +7 -0
  55. package/.next/standalone/.next/server/app/api/v1/events/route.js +26 -32
  56. package/.next/standalone/.next/server/app/api/v1/events/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/v1/extension/browser/activate/route.js +7 -0
  59. package/.next/standalone/.next/server/app/api/v1/extension/browser/foreground/route.js +7 -0
  60. package/.next/standalone/.next/server/app/api/v1/extension/browser/history/route.js +7 -0
  61. package/.next/standalone/.next/server/app/api/v1/extension/browser/poll/route.js +7 -0
  62. package/.next/standalone/.next/server/app/api/v1/extension/browser/progress/route.js +7 -0
  63. package/.next/standalone/.next/server/app/api/v1/extension/browser/result/route.js +7 -0
  64. package/.next/standalone/.next/server/app/api/v1/extension/browser/retry/route.js +7 -0
  65. package/.next/standalone/.next/server/app/api/v1/extension/browser/status/route.js +7 -0
  66. package/.next/standalone/.next/server/app/api/v1/extension/browser/tabs/route.js +7 -0
  67. package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js +1 -1
  68. package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js +1 -1
  69. package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js +1 -1
  70. package/.next/standalone/.next/server/app/api/v1/extensions/route.js +1 -1
  71. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/config/route.js +1 -1
  72. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/route.js +1 -1
  73. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +1 -1
  74. package/.next/standalone/.next/server/app/api/v1/harnesses/[id]/route.js +7 -0
  75. package/.next/standalone/.next/server/app/api/v1/harnesses/default/route.js +7 -0
  76. package/.next/standalone/.next/server/app/api/v1/harnesses/route.js +7 -0
  77. package/.next/standalone/.next/server/app/api/v1/health/route.js +17 -5
  78. package/.next/standalone/.next/server/app/api/v1/integrations/[name]/route.js +7 -0
  79. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/callback/route.js +7 -0
  80. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/start/route.js +7 -0
  81. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/status/route.js +7 -0
  82. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/callback/route.js +7 -0
  83. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/start/route.js +7 -0
  84. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/status/route.js +7 -0
  85. package/.next/standalone/.next/server/app/api/v1/integrations/route.js +7 -0
  86. package/.next/standalone/.next/server/app/api/v1/lifecycle/adoption/route.js +17 -5
  87. package/.next/standalone/.next/server/app/api/v1/maps/embed/route.js +7 -0
  88. package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js +17 -5
  89. package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js +17 -5
  90. package/.next/standalone/.next/server/app/api/v1/memory/[namespace]/[key]/route.js +7 -0
  91. package/.next/standalone/.next/server/app/api/v1/memory/policy/route.js +7 -0
  92. package/.next/standalone/.next/server/app/api/v1/memory/route.js +7 -0
  93. package/.next/standalone/.next/server/app/api/v1/memory/search/route.js +7 -0
  94. package/.next/standalone/.next/server/app/api/v1/models/[name]/compact-threads/route.js +7 -0
  95. package/.next/standalone/.next/server/app/api/v1/models/[name]/route.js +7 -0
  96. package/.next/standalone/.next/server/app/api/v1/models/route.js +7 -0
  97. package/.next/standalone/.next/server/app/api/v1/packages/defaults/[id]/route.js +28 -4
  98. package/.next/standalone/.next/server/app/api/v1/packages/reload/route.js +28 -4
  99. package/.next/standalone/.next/server/app/api/v1/packages/route.js +28 -4
  100. package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +1 -1
  101. package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js +27 -12
  102. package/.next/standalone/.next/server/app/api/v1/profile/location/route.js +7 -0
  103. package/.next/standalone/.next/server/app/api/v1/profile/route.js +7 -0
  104. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +585 -58
  105. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js +7 -0
  106. package/.next/standalone/.next/server/app/api/v1/providers/github-copilot/auth/route.js +7 -0
  107. package/.next/standalone/.next/server/app/api/v1/providers/route.js +7 -0
  108. package/.next/standalone/.next/server/app/api/v1/proxy-config/route.js +7 -0
  109. package/.next/standalone/.next/server/app/api/v1/redaction/init/route.js +7 -0
  110. package/.next/standalone/.next/server/app/api/v1/redaction/route.js +7 -0
  111. package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/[id]/route.js +17 -5
  112. package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/[id]/run/route.js +1 -1
  113. package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/route.js +7 -0
  114. package/.next/standalone/.next/server/app/api/v1/security/idle-timeout/route.js +7 -0
  115. package/.next/standalone/.next/server/app/api/v1/security/pin/route.js +7 -0
  116. package/.next/standalone/.next/server/app/api/v1/security/state/route.js +7 -0
  117. package/.next/standalone/.next/server/app/api/v1/security/unlock/route.js +7 -0
  118. package/.next/standalone/.next/server/app/api/v1/security/verify-pin/route.js +7 -0
  119. package/.next/standalone/.next/server/app/api/v1/skills/[id]/route.js +7 -0
  120. package/.next/standalone/.next/server/app/api/v1/skills/repos/[id]/route.js +7 -0
  121. package/.next/standalone/.next/server/app/api/v1/skills/repos/route.js +7 -0
  122. package/.next/standalone/.next/server/app/api/v1/skills/route.js +7 -0
  123. package/.next/standalone/.next/server/app/api/v1/tasks/[agent_id]/route.js +7 -0
  124. package/.next/standalone/.next/server/app/api/v1/tasks/route.js +7 -0
  125. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/activity/route.js +7 -0
  126. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/context-pin/route.js +12 -6
  127. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js +125 -26
  128. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +1 -2
  129. package/.next/standalone/.next/server/app/api/v1/threads/route.js +17 -5
  130. package/.next/standalone/.next/server/app/api/v1/tools/route.js +1 -1
  131. package/.next/standalone/.next/server/app/api/v1/update/apply/route.js.nft.json +1 -1
  132. package/.next/standalone/.next/server/app/api/v1/voice/transcribe/route.js +17 -5
  133. package/.next/standalone/.next/server/app/api/v1/voice/tts/route.js +17 -5
  134. package/.next/standalone/.next/server/app/api/v1/watchers/[id]/route.js +17 -5
  135. package/.next/standalone/.next/server/app/api/v1/watchers/[id]/run/route.js +1 -1
  136. package/.next/standalone/.next/server/app/api/v1/watchers/route.js +1 -1
  137. package/.next/standalone/.next/server/app/page.js +27323 -27034
  138. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  139. package/.next/standalone/.next/server/app/setup/page.js +8 -0
  140. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  141. package/.next/standalone/.next/server/app-paths-manifest.json +5 -5
  142. package/.next/standalone/.next/server/chunks/1388.js +106 -36
  143. package/.next/standalone/.next/server/chunks/2068.js +47 -10
  144. package/.next/standalone/.next/server/chunks/210.js +6 -6
  145. package/.next/standalone/.next/server/chunks/2447.js +10 -5
  146. package/.next/standalone/.next/server/chunks/2653.js +80 -1
  147. package/.next/standalone/.next/server/chunks/3042.js +10 -8
  148. package/.next/standalone/.next/server/chunks/{4540.js → 309.js} +104 -38
  149. package/.next/standalone/.next/server/chunks/{2350.js → 3272.js} +104 -37
  150. package/.next/standalone/.next/server/chunks/3524.js +150 -67
  151. package/.next/standalone/.next/server/chunks/3566.js +1 -1
  152. package/.next/standalone/.next/server/chunks/4189.js +1 -1
  153. package/.next/standalone/.next/server/chunks/4596.js +28 -4
  154. package/.next/standalone/.next/server/chunks/46.js +91 -31
  155. package/.next/standalone/.next/server/chunks/466.js +53 -7
  156. package/.next/standalone/.next/server/chunks/4955.js +30 -3
  157. package/.next/standalone/.next/server/chunks/5585.js +28 -4
  158. package/.next/standalone/.next/server/chunks/5908.js +63 -42
  159. package/.next/standalone/.next/server/chunks/6142.js +10 -2
  160. package/.next/standalone/.next/server/chunks/8461.js +10 -8
  161. package/.next/standalone/.next/server/chunks/9032.js +5 -5
  162. package/.next/standalone/.next/server/chunks/9164.js +487 -177
  163. package/.next/standalone/.next/server/chunks/9304.js +51 -12
  164. package/.next/standalone/.next/server/chunks/{423.js → 9590.js} +84 -24
  165. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  166. package/.next/standalone/.next/server/middleware.js +89 -21
  167. package/.next/standalone/.next/server/pages/404.html +14 -2
  168. package/.next/standalone/.next/server/pages/500.html +1 -1
  169. package/.next/standalone/.next/server/preview-props.json +3 -3
  170. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  171. package/.next/standalone/.next/static/chunks/{1395-e4a99912b8f83044.js → 1395-bfe1f3ef760c0f50.js} +54 -8
  172. package/.next/standalone/.next/static/chunks/app/{layout-9bc0b10127cb7d42.js → layout-7b24ad13faa3944b.js} +69 -2
  173. package/.next/standalone/.next/static/chunks/app/{page-213a623b37226452.js → page-2ef8d75d40eb4311.js} +10643 -10295
  174. package/.next/standalone/.next/static/css/c160ea862133acd2.css +5 -0
  175. package/.next/standalone/docs/adoption/changesets.json +2805 -0
  176. package/.next/standalone/package.json +1 -1
  177. package/CHANGELOG.md +52 -0
  178. package/api/client.models.test.ts +10 -0
  179. package/api/client.startup.test.ts +129 -0
  180. package/api/client.ts +51 -9
  181. package/api/thread-metadata.test.ts +72 -0
  182. package/api/types.ts +6 -3
  183. package/app/api/v1/providers/[provider]/models/route.test.ts +216 -6
  184. package/app/api/v1/providers/[provider]/models/route.ts +259 -40
  185. package/app/api/v1/threads/[thread_id]/route.ts +26 -23
  186. package/app/api/v1/threads/[thread_id]/run/route.ts +0 -1
  187. package/app/globals.css +119 -29
  188. package/app/layout.tsx +12 -0
  189. package/components/agents/AgentsPanel.tsx +5 -13
  190. package/components/chat/ChatView.tsx +9 -18
  191. package/components/chat/InputBar.tsx +1 -1
  192. package/components/chat/MessageBubble.tsx +13 -1
  193. package/components/chat/MessageList.test.tsx +45 -4
  194. package/components/chat/MessageList.tsx +123 -89
  195. package/components/chat/ToolList.tsx +88 -84
  196. package/components/chat/useThreadActivity.test.tsx +2 -2
  197. package/components/chat/useThreadActivity.ts +2 -2
  198. package/components/chat/useThreadData.ts +1 -30
  199. package/components/chat/useThreadMetadataPolling.test.tsx +128 -0
  200. package/components/chat/useThreadMetadataPolling.ts +50 -0
  201. package/components/chat/useUserProfileAndAgent.test.tsx +31 -0
  202. package/components/chat/useUserProfileAndAgent.ts +13 -2
  203. package/components/dashboard/DashboardPanel.tsx +1 -1
  204. package/components/dashboard/Series.tsx +34 -21
  205. package/components/dashboard/SummaryCards.tsx +1 -1
  206. package/components/integrations/NetworkSection.tsx +2 -2
  207. package/components/layout/AppShell.tsx +22 -7
  208. package/components/layout/MenuPanel.tsx +14 -24
  209. package/components/profile/ProfilePanel.tsx +2 -2
  210. package/components/settings/AppearancePanel.tsx +70 -3
  211. package/components/settings/SettingsPanel.tsx +2 -2
  212. package/components/tools/ToolsPanel.tsx +2 -7
  213. package/components/ui/AppearancePreferences.test.tsx +68 -0
  214. package/components/ui/BootScreen.test.tsx +44 -0
  215. package/components/ui/BootScreen.tsx +24 -11
  216. package/components/ui/NotificationStatus.test.tsx +2 -2
  217. package/components/ui/NotificationStatus.tsx +5 -5
  218. package/components/ui/SubTabBar.tsx +2 -3
  219. package/contexts/ThemeContext.tsx +84 -1
  220. package/hooks/useAgentSession.test.ts +12 -1
  221. package/hooks/useAgentSession.ts +6 -4
  222. package/hooks/useUrlSync.test.ts +13 -0
  223. package/hooks/useUrlSync.ts +0 -4
  224. package/lib/agents/citation-checker.test.ts +40 -2
  225. package/lib/agents/citation-checker.ts +56 -11
  226. package/lib/agents/context-boundary.ts +5 -6
  227. package/lib/agents/instruction-edits.test.ts +120 -0
  228. package/lib/agents/instruction-edits.ts +65 -42
  229. package/lib/agents/prepare/request.ts +0 -2
  230. package/lib/agents/prepare/system-prompt.test.ts +10 -0
  231. package/lib/agents/prepare/system-prompt.ts +8 -2
  232. package/lib/agents/proposals.test.ts +76 -0
  233. package/lib/agents/proposals.ts +13 -5
  234. package/lib/agents/run-thread.auto-boundary.test.ts +125 -5
  235. package/lib/agents/run-thread.ts +24 -9
  236. package/lib/agents/sqlite-checkpoint-saver.test.ts +91 -0
  237. package/lib/agents/warm-summary-background.ts +22 -2
  238. package/lib/agents/warm-topics.test.ts +155 -1
  239. package/lib/agents/warm-topics.ts +27 -7
  240. package/lib/bridges/dispatcher.ts +1 -0
  241. package/lib/bridges/message-role.test.ts +81 -0
  242. package/lib/bridges/message-role.ts +23 -10
  243. package/lib/bridges/types.ts +6 -0
  244. package/lib/bridges/whatsapp-link.test.ts +32 -0
  245. package/lib/bridges/whatsapp-link.ts +21 -0
  246. package/lib/bridges/whatsapp.inbound.test.ts +239 -0
  247. package/lib/bridges/whatsapp.sender.test.ts +80 -1
  248. package/lib/bridges/whatsapp.ts +76 -38
  249. package/lib/db/migrations.test.ts +61 -0
  250. package/lib/db/migrations.ts +13 -0
  251. package/lib/embeddings/performance.test.ts +114 -0
  252. package/lib/pricing/snapshot.test.ts +70 -0
  253. package/lib/pricing/snapshot.ts +10 -2
  254. package/lib/proxy/dispatcher.test.ts +119 -1
  255. package/lib/proxy/dispatcher.ts +22 -9
  256. package/lib/stores/agent-configs.ts +10 -5
  257. package/lib/stores/bridges.test.ts +81 -0
  258. package/lib/stores/bridges.ts +9 -8
  259. package/lib/stores/dashboard-metrics-cache.test.ts +99 -0
  260. package/lib/stores/dashboard-metrics.ts +30 -4
  261. package/lib/stores/message-embedding-jobs.test.ts +27 -0
  262. package/lib/stores/message-embedding-jobs.ts +5 -3
  263. package/lib/stores/message-usage.test.ts +21 -1
  264. package/lib/stores/pricing-cache.test.ts +31 -0
  265. package/lib/stores/pricing.ts +3 -0
  266. package/lib/stores/proxy-config.test.ts +120 -0
  267. package/lib/stores/proxy-config.ts +22 -4
  268. package/lib/stores/threads.ts +12 -8
  269. package/lib/tools/communications/calendar.test.ts +43 -2
  270. package/lib/tools/communications/calendar.ts +2 -0
  271. package/lib/tools/communications/gmail.test.ts +51 -1
  272. package/lib/tools/communications/gmail.ts +44 -2
  273. package/lib/tools/communications/outlook-calendar.ts +2 -0
  274. package/lib/tools/communications/outlook.test.ts +33 -2
  275. package/lib/tools/communications/outlook.ts +2 -0
  276. package/lib/tools/filesystem/edit-match.test.ts +133 -0
  277. package/lib/tools/filesystem/edit-match.ts +7 -2
  278. package/lib/tools/packages/default-packages.test.ts +21 -0
  279. package/lib/tools/packages/optional-package.test.ts +72 -0
  280. package/lib/tools/packages/optional-package.ts +33 -4
  281. package/lib/tools/support/result-refs.test.ts +131 -0
  282. package/lib/tools/support/result-refs.ts +41 -9
  283. package/lib/ui/navigate.test.ts +13 -0
  284. package/lib/ui/navigate.ts +10 -2
  285. package/lib/utils/source-link.test.ts +51 -0
  286. package/lib/utils/source-link.ts +45 -0
  287. package/package.json +1 -1
  288. package/.next/standalone/.next/static/css/312542a5725ba0d5.css +0 -5
  289. /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → 544OFJV-q0RC6Gjgje4LT}/_buildManifest.js +0 -0
  290. /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → 544OFJV-q0RC6Gjgje4LT}/_ssgManifest.js +0 -0
@@ -678,9 +678,10 @@ function isIntegrationProbe(name) {
678
678
  /* harmony export */ });
679
679
  /* unused harmony exports outlookSearchTool, outlookGetMessageTool, outlookListFoldersTool, outlookModifyMessageTool, outlookCreateDraftTool, outlookTrashMessageTool, outlookSendEmailTool */
680
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);
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)({
@@ -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) {
@@ -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],
@@ -10698,6 +10718,8 @@ var pricing = __webpack_require__(95601);
10698
10718
 
10699
10719
  const CHARS_PER_TOKEN = 4;
10700
10720
  const DEFAULT_WINDOW_DAYS = 30;
10721
+ const METRICS_CACHE_MS = 5000;
10722
+ let metricsCache = null;
10701
10723
  function recalculateSnapshotCost(input, tables) {
10702
10724
  const rates = (0,pricing/* modelRatesFor */.En)(tables, input.provider, input.modelId);
10703
10725
  if (rates.inputPer1M == null && rates.outputPer1M == null) return input.recordedCostUsd;
@@ -10710,7 +10732,17 @@ async function getDashboardMetrics(days = DEFAULT_WINDOW_DAYS) {
10710
10732
  const now = new Date();
10711
10733
  const boundedDays = Number.isFinite(days) ? Math.min(120, Math.max(7, Math.floor(days))) : DEFAULT_WINDOW_DAYS;
10712
10734
  const since = new Date(now.getTime() - boundedDays * 24 * 60 * 60 * 1000).toISOString();
10713
- const usageRows = (0,lib_db/* getDb */.L)().prepare(`SELECT m.created_at, m.role, m.content, m.tool_events, m.thread_id, t.agent_id,
10735
+ const db = (0,lib_db/* getDb */.L)();
10736
+ const tables = (0,pricing/* getPricingTables */.iH)();
10737
+ // total_changes covers this connection; data_version covers other writers.
10738
+ const { changes } = db.prepare("SELECT total_changes() AS changes").get();
10739
+ const { data_version: dataVersion } = db.prepare("PRAGMA data_version").get();
10740
+ if (metricsCache?.db === db && metricsCache.changes === changes && metricsCache.dataVersion === dataVersion && metricsCache.tables === tables && metricsCache.metrics.days === boundedDays && metricsCache.metrics.generated_at.slice(0, 10) === now.toISOString().slice(0, 10) && now.getTime() - metricsCache.createdAt >= 0 && now.getTime() - metricsCache.createdAt < METRICS_CACHE_MS) {
10741
+ return metricsCache.metrics;
10742
+ }
10743
+ const usageRows = db.prepare(`SELECT m.created_at, m.role,
10744
+ CASE WHEN mu.input_tokens IS NULL THEN m.content ELSE '' END AS content,
10745
+ m.tool_events, m.thread_id, t.agent_id,
10714
10746
  a.name AS agent_name, mc.provider AS provider,
10715
10747
  mc.model_id AS model_id,
10716
10748
  COALESCE(ta.model_config_name, a.model_config_name, dmc.name) AS model_config_name,
@@ -10760,7 +10792,6 @@ async function getDashboardMetrics(days = DEFAULT_WINDOW_DAYS) {
10760
10792
  snapshotThreadIds.add(r.thread_id);
10761
10793
  }
10762
10794
  }
10763
- const tables = (0,pricing/* getPricingTables */.iH)();
10764
10795
  const { byProvider, generatedAt } = tables;
10765
10796
  const dayMap = seedDayBuckets(now, boundedDays);
10766
10797
  const agentMap = new Map();
@@ -11076,7 +11107,7 @@ async function getDashboardMetrics(days = DEFAULT_WINDOW_DAYS) {
11076
11107
  by_model: dayModels
11077
11108
  };
11078
11109
  }
11079
- return {
11110
+ const metrics = {
11080
11111
  generated_at: now.toISOString(),
11081
11112
  days: boundedDays,
11082
11113
  summary: {
@@ -11132,6 +11163,15 @@ async function getDashboardMetrics(days = DEFAULT_WINDOW_DAYS) {
11132
11163
  notes: "Dashboard cost is recalculated from captured provider token usage and the latest matching rates. The original per-turn cost snapshots remain unchanged; legacy rows use content-length token estimates."
11133
11164
  }
11134
11165
  };
11166
+ metricsCache = {
11167
+ db,
11168
+ changes,
11169
+ dataVersion,
11170
+ tables,
11171
+ createdAt: now.getTime(),
11172
+ metrics
11173
+ };
11174
+ return metrics;
11135
11175
  }
11136
11176
  function seedDayBuckets(now, days) {
11137
11177
  const out = new Map();
@@ -11675,6 +11715,7 @@ const PRICE_LINE_RE = new RegExp([
11675
11715
  String.raw`\$\s*\d+(?:\.\d+)?\s*(?:/|per)\s*(?:image|minute|request)`
11676
11716
  ].join("|"), "gi");
11677
11717
  const GOOGLE_RESULT_RE = /href="\/url\?q=([^"&]+)[^"]*"/gi;
11718
+ const GOOGLE_OWN_HOST_RE = /^(?:www|accounts|support|maps|policies|webcache)?\.?google\.[a-z.]+$/i;
11678
11719
  function toCanonicalProvider(id) {
11679
11720
  const lower = id.toLowerCase();
11680
11721
  if (lower.includes("google") || lower.includes("gemini")) return "google";
@@ -11706,7 +11747,8 @@ async function refreshPricingSnapshot(opts) {
11706
11747
  const existing = await readPricingSnapshot();
11707
11748
  if (!force && existing) {
11708
11749
  const ageDays = (Date.now() - Date.parse(existing.generated_at)) / (24 * 60 * 60 * 1000);
11709
- if (Number.isFinite(ageDays) && ageDays < ttlDays) {
11750
+ // A future generated_at (clock skew, copied snapshot) must not pin the cache as fresh.
11751
+ if (Number.isFinite(ageDays) && ageDays >= 0 && ageDays < ttlDays) {
11710
11752
  return {
11711
11753
  refreshed: false,
11712
11754
  reason: "fresh",
@@ -11843,6 +11885,8 @@ function parseGoogleResultLinks(html) {
11843
11885
  const out = [];
11844
11886
  const seen = new Set();
11845
11887
  let m;
11888
+ // Shared /g regex: an early break leaves lastIndex mid-string for the next page.
11889
+ GOOGLE_RESULT_RE.lastIndex = 0;
11846
11890
  while((m = GOOGLE_RESULT_RE.exec(html)) !== null){
11847
11891
  const raw = m[1] ?? "";
11848
11892
  let url = "";
@@ -11852,7 +11896,11 @@ function parseGoogleResultLinks(html) {
11852
11896
  continue;
11853
11897
  }
11854
11898
  if (!/^https?:\/\//i.test(url)) continue;
11855
- if (/\/google\./i.test(url)) continue;
11899
+ try {
11900
+ if (GOOGLE_OWN_HOST_RE.test(new URL(url).hostname)) continue;
11901
+ } catch {
11902
+ continue;
11903
+ }
11856
11904
  if (seen.has(url)) continue;
11857
11905
  seen.add(url);
11858
11906
  out.push(url);
@@ -13511,58 +13559,79 @@ function containsText(source, needle, ignoreCase) {
13511
13559
  if (!ignoreCase) return source.includes(needle);
13512
13560
  return source.toLowerCase().includes(needle.toLowerCase());
13513
13561
  }
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()
13562
+ const FENCE_RE = /^\s{0,3}(`{3,}|~{3,})/;
13563
+ const HORIZONTAL_RULE_RE = /^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/;
13564
+ /**
13565
+ * Drop duplicate segments from `parts`, which alternates content (even
13566
+ * indexes) and the original separator that follows it (odd indexes).
13567
+ * Separators are preserved verbatim, so only removed segments change the
13568
+ * text. `keyOf` returns null for a segment that must always be kept.
13569
+ */ function dedupeSegments(parts, keyOf, keep) {
13570
+ const contentIdx = parts.map((_, i)=>i).filter((i)=>i % 2 === 0);
13571
+ const visit = keep === "first" ? contentIdx : [
13572
+ ...contentIdx
13525
13573
  ].reverse();
13526
13574
  const seen = new Set();
13575
+ const dropped = new Set();
13527
13576
  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);
13577
+ const key = keyOf(idx);
13578
+ if (key === null) continue;
13579
+ if (seen.has(key)) dropped.add(idx);
13580
+ else seen.add(key);
13581
+ }
13582
+ if (dropped.size === 0) return {
13583
+ text: parts.join(""),
13584
+ removed: 0
13585
+ };
13586
+ let text = "";
13587
+ let emitted = false;
13588
+ for (const idx of contentIdx){
13589
+ if (dropped.has(idx)) continue;
13590
+ if (emitted && idx > 0) text += parts[idx - 1];
13591
+ text += parts[idx];
13592
+ emitted = true;
13536
13593
  }
13537
- const next = lines.filter((_, idx)=>keep.has(idx));
13538
13594
  return {
13539
- text: next.join("\n"),
13540
- removed: lines.length - next.length
13595
+ text,
13596
+ removed: dropped.size
13541
13597
  };
13542
13598
  }
13599
+ function dedupeLines(source, opts) {
13600
+ const parts = source.split(/(\r?\n)/);
13601
+ // Fence markers, fenced code and horizontal rules repeat by design; a
13602
+ // closing ``` is always a "duplicate" of the opening one.
13603
+ const protectedIdx = new Set();
13604
+ let fence = null;
13605
+ for(let i = 0; i < parts.length; i += 2){
13606
+ const marker = FENCE_RE.exec(parts[i])?.[1];
13607
+ if (fence !== null) {
13608
+ protectedIdx.add(i);
13609
+ if (marker && marker[0] === fence[0] && marker.length >= fence.length && /^\s*[`~]+\s*$/.test(parts[i])) fence = null;
13610
+ } else if (marker) {
13611
+ protectedIdx.add(i);
13612
+ fence = marker;
13613
+ } else if (HORIZONTAL_RULE_RE.test(parts[i])) {
13614
+ protectedIdx.add(i);
13615
+ }
13616
+ }
13617
+ return dedupeSegments(parts, (i)=>{
13618
+ if (protectedIdx.has(i)) return null;
13619
+ const normalized = opts.trim ? parts[i].trim() : parts[i];
13620
+ if (opts.skipBlank && normalized.length === 0) return null;
13621
+ return opts.ignoreCase ? normalized.toLowerCase() : normalized;
13622
+ }, opts.keep);
13623
+ }
13543
13624
  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;
13625
+ const tail = /\s*$/.exec(source)?.[0] ?? "";
13626
+ const parts = source.slice(0, source.length - tail.length).split(/(\r?\n(?:[ \t]*\r?\n)+)/);
13627
+ const out = dedupeSegments(parts, (i)=>{
13628
+ if (parts[i].trim().length === 0) return null;
13629
+ const base = opts.trim ? parts[i].trim() : parts[i];
13547
13630
  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));
13631
+ }, opts.keep);
13563
13632
  return {
13564
- text: next.join("\n\n"),
13565
- removed: paragraphs.length - next.length
13633
+ text: out.text + tail,
13634
+ removed: out.removed
13566
13635
  };
13567
13636
  }
13568
13637
 
@@ -14363,7 +14432,7 @@ function version_getCurrentVersion() {
14363
14432
  }
14364
14433
 
14365
14434
  ;// ./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":[]}]}]}');
14435
+ 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":[]},{"version":"2.5.0","previous_version":"2.4.0","date":"2026-10-18","hints":[],"commits":["perf(chat): reuse startup reads when opening an agent","fix(chat): use available width and contain tool pill content","perf(db): use indexed reads and reuse chat history renders","fix(ui): align configuration panel widths","fix(packages): use workspace fallback for optional packages","feat(ui): add appearance presets and reorganize panels","fix(models): align live catalog capability metadata","fix(models): update provider model catalogs"],"prompts":[{"id":"system.Enabled tools","source":"lib/agents/prepare/system-prompt.ts","change":"changed","added_lines":["You can execute the 83 tool(s) listed below directly, plus 0 further permitted tool(s) through invoke_tool. 166 known tool(s) are not enabled for this agent; 0 known tool(s) are globally unavailable."],"removed_lines":["You can execute the 83 tool(s) listed below directly, plus 0 further permitted tool(s) through invoke_tool. 39 known tool(s) are not enabled for this agent; 0 known tool(s) are globally unavailable."],"omitted_lines":0}],"tools":[{"name":"confluence_add_comment","change":"added","description_changed":false,"description":"Add a footer comment to a Confluence page (v2). Pass exactly one of `body_text` or `body_storage`. Pass `parent_comment_id` (from confluence_get_comments) to reply in a thread.","params_added":["async_run","body_storage","body_text","deadline_ms","page_id","parent_comment_id","stream"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_add_label","change":"added","description_changed":false,"description":"Add one or more labels to a Confluence page (additive — does not replace existing labels). Uses the v1 endpoint (v2 only reads labels as of 2026).","params_added":["async_run","deadline_ms","labels","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["labels","page_id"],"required_removed":[]},{"name":"confluence_create_page","change":"added","description_changed":false,"description":"Create a Confluence page (v2). Pass `space_key` (e.g. \'ENG\') — auto-resolved to v2 spaceId. Pass exactly one of `body_text` (plain text → storage XHTML automatically) or `body_storage` (raw XHTML for advanced edits). `parent_id` makes it a child page. Disable to make the agent unable to author Confluence pages.","params_added":["async_run","body_storage","body_text","deadline_ms","parent_id","space_key","stream","title"],"params_removed":[],"params_changed":[],"required_added":["space_key","title"],"required_removed":[]},{"name":"confluence_delete_attachment","change":"added","description_changed":false,"description":"Delete a Confluence attachment by id (v2). Default soft-deletes (trash, restorable). Pass purge=true for permanent deletion, which **also requires `confirm` to equal attachment_id** as a guardrail.","params_added":["async_run","attachment_id","confirm","deadline_ms","purge","stream"],"params_removed":[],"params_changed":[],"required_added":["attachment_id"],"required_removed":[]},{"name":"confluence_delete_comment","change":"added","description_changed":false,"description":"Permanently delete a Confluence comment (v2). Pass `kind: \'footer\' | \'inline\'`. **Destructive — no undo.** Disable to make the agent unable to delete comments.","params_added":["async_run","comment_id","deadline_ms","kind","stream"],"params_removed":[],"params_changed":[],"required_added":["comment_id","kind"],"required_removed":[]},{"name":"confluence_delete_page","change":"added","description_changed":false,"description":"Delete a Confluence page (v2). Default soft-deletes (page goes to trash, restorable). Pass purge=true for permanent deletion, which **also requires `confirm` to equal page_id** as a guardrail. Disable to make the agent unable to delete pages.","params_added":["async_run","confirm","deadline_ms","page_id","purge","stream"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_get_attachment_content","change":"added","description_changed":false,"description":"Fetch an attachment\'s bytes by its download_link (from confluence_list_attachments). Returns UTF-8 text (capped at 50KB) for text-like content types, or base64 for binary. Override the auto-detection via `as_text`.","params_added":["as_text","async_run","deadline_ms","download_link","stream"],"params_removed":[],"params_changed":[],"required_added":["download_link"],"required_removed":[]},{"name":"confluence_get_comments","change":"added","description_changed":false,"description":"List footer comments (and inline comments by default) on a Confluence page. Returns id, version, author_id, created_at, body_storage, parent_comment_id (for threading). Tolerates the known v2 inline-comments 404 bug — surfaces it as `inline_warning` rather than failing.","params_added":["async_run","deadline_ms","include_inline","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_get_labels","change":"added","description_changed":false,"description":"List labels on a Confluence page (cursor-paginated). Default limit 50 (max 250).","params_added":["async_run","cursor","deadline_ms","limit","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_get_page","change":"added","description_changed":false,"description":"Fetch a Confluence page by id (v2). Returns title, space_id, parent_id, version, and BOTH body_storage (round-trippable into confluence_update_page) and body_view (rendered HTML, easier to summarize). Each body capped at 20KB.","params_added":["async_run","deadline_ms","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_get_page_ancestors","change":"added","description_changed":false,"description":"Return the parent chain (root → leaf) for a Confluence page. Useful for breadcrumbs and understanding where a page lives in the tree.","params_added":["async_run","deadline_ms","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_get_page_by_title","change":"added","description_changed":false,"description":"Find Confluence page(s) by exact title within a space. Auto-resolves `space_key` (e.g. \'ENG\') to the v2 space id. Returns up to 5 matches; pass `include_body: true` to also include storage XHTML.","params_added":["async_run","deadline_ms","include_body","space_key","stream","title"],"params_removed":[],"params_changed":[],"required_added":["space_key","title"],"required_removed":[]},{"name":"confluence_get_page_children","change":"added","description_changed":false,"description":"List direct children of a Confluence page (cursor-paginated). Pass `cursor` from a prior call\'s `next_cursor` to fetch the next page. Default limit 25 (max 250).","params_added":["async_run","cursor","deadline_ms","limit","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_list_attachments","change":"added","description_changed":false,"description":"List attachments on a Confluence page (cursor-paginated). Returns id, title, media_type, file_size, download_link. Use confluence_get_attachment_content with the download_link to fetch bytes.","params_added":["async_run","cursor","deadline_ms","limit","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_list_spaces","change":"added","description_changed":false,"description":"List Confluence spaces (cursor-paginated). Returns id, key, name, type, status, homepage_id. Useful for discovering space keys to pass to confluence_create_page or confluence_get_page_by_title.","params_added":["async_run","cursor","deadline_ms","limit","status","stream","type"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"confluence_move_page","change":"added","description_changed":false,"description":"Reorder/reparent a Confluence page (v2). `position`: \'before\' or \'after\' to place as a sibling of `target_id`; \'append\' to make it a child of `target_id`. Non-destructive.","params_added":["async_run","deadline_ms","page_id","position","stream","target_id"],"params_removed":[],"params_changed":[],"required_added":["page_id","position","target_id"],"required_removed":[]},{"name":"confluence_remove_label","change":"added","description_changed":false,"description":"Remove a single label from a Confluence page. Uses the v1 endpoint (v2 Label group is still read-only as of 2026). Counterpart to confluence_add_label.","params_added":["async_run","deadline_ms","label","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["label","page_id"],"required_removed":[]},{"name":"confluence_search","change":"added","description_changed":false,"description":"Search Confluence pages with CQL (Confluence Query Language). Examples: \'type=page AND title~\\"runbook\\"\', \'space=ENG AND lastmodified > now(\\"-7d\\")\'.","params_added":["async_run","cql","deadline_ms","max_results","stream"],"params_removed":[],"params_changed":[],"required_added":["cql"],"required_removed":[]},{"name":"confluence_update_comment","change":"added","description_changed":false,"description":"Edit an existing Confluence comment (v2). Pass `kind: \'footer\' | \'inline\'` to route to the correct endpoint. Same `body_text` xor `body_storage` pattern as confluence_add_comment. If version_number is omitted, the tool auto-fetches the current version and sends current+1 (Confluence requires strict +1 increments).","params_added":["async_run","body_storage","body_text","comment_id","deadline_ms","kind","stream","version_message","version_number"],"params_removed":[],"params_changed":[],"required_added":["comment_id","kind"],"required_removed":[]},{"name":"confluence_update_page","change":"added","description_changed":false,"description":"Update an existing Confluence page (v2). Pass exactly one of `body_text` or `body_storage`. If `version_number` is omitted, the tool auto-fetches the current version and sends current+1 (Confluence requires strict +1 increments; gaps cause 409). If `title` is omitted, the existing title is preserved. Avoid back-to-back updates within ~1 second — Confluence may return 409 even with the correct version. Disable to make the agent read-only on pages.","params_added":["async_run","body_storage","body_text","deadline_ms","page_id","stream","title","version_message","version_number"],"params_removed":[],"params_changed":[],"required_added":["page_id"],"required_removed":[]},{"name":"confluence_upload_attachment","change":"added","description_changed":false,"description":"Attach a file to a Confluence page. Pass content_base64 for binary or content_text for plain UTF-8. Uses the v1 multipart endpoint (v2 has no attachment-create endpoint as of 2026). Disable to make the agent unable to add attachments.","params_added":["async_run","comment","content_base64","content_text","deadline_ms","filename","minor_edit","page_id","stream"],"params_removed":[],"params_changed":[],"required_added":["filename","page_id"],"required_removed":[]},{"name":"github_add_comment","change":"added","description_changed":false,"description":"Post a comment on an issue or pull request (GitHub treats both the same). **PREFER THIS over shell-exec\'ing the `gh` CLI.** Body is rendered as GitHub-flavored Markdown.","params_added":["async_run","body","deadline_ms","number","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["body","number","owner","repo"],"required_removed":[]},{"name":"github_create_issue","change":"added","description_changed":false,"description":"Open a new issue in a repository. **PREFER THIS over shell-exec\'ing the `gh` CLI.** Labels and assignees must already exist on the repo — invalid values produce a 422.","params_added":["assignees","async_run","body","deadline_ms","labels","owner","repo","stream","title"],"params_removed":[],"params_changed":[],"required_added":["owner","repo","title"],"required_removed":[]},{"name":"github_create_pull","change":"added","description_changed":false,"description":"Open a pull request. **PREFER THIS over shell-exec\'ing the `gh` CLI.** `head` is the branch with your changes (use `user:branch` for cross-fork PRs); `base` is the branch you want to merge into. Returns 422 if the head/base pair has no diff or if the branches don\'t exist.","params_added":["async_run","base","body","deadline_ms","draft","head","maintainer_can_modify","owner","repo","stream","title"],"params_removed":[],"params_changed":[],"required_added":["base","head","owner","repo","title"],"required_removed":[]},{"name":"github_create_review","change":"added","description_changed":false,"description":"Submit a pull-request review (approve / request changes / leave a comment). **PREFER THIS over shell-exec\'ing the `gh` CLI.** `event=APPROVE` doesn\'t require a body. `event=REQUEST_CHANGES` requires a body. Line-level inline review comments aren\'t supported by this tool yet — use the GitHub UI for those.","params_added":["async_run","body","commit_id","deadline_ms","event","number","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["event","number","owner","repo"],"required_removed":[]},{"name":"github_get_file","change":"added","description_changed":false,"description":"Read a file\'s contents from a repo at an optional ref (branch / tag / commit SHA). **PREFER THIS over shell-exec\'ing the `gh` CLI.** Returns up to 20 KB of UTF-8 text; longer files are truncated with `truncated: true`. Binary files return `binary: true` and `size_bytes` instead of `content`. GitHub itself rejects files larger than ~1 MB on this endpoint.","params_added":["async_run","deadline_ms","owner","path","ref","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["owner","path","repo"],"required_removed":[]},{"name":"github_get_issue","change":"added","description_changed":false,"description":"Fetch a single issue (or PR — same endpoint) by number. Returns body capped at 20KB, plus labels, assignees, and timestamps. **PREFER THIS over shell-exec\'ing the `gh` CLI.**","params_added":["async_run","deadline_ms","number","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"github_get_pull","change":"added","description_changed":false,"description":"Fetch detail on a single pull request — head/base SHAs, mergeable state, additions/deletions, changed files count, review comment count. **PREFER THIS over shell-exec\'ing the `gh` CLI.** Note: `mergeable` may be null on a freshly-pushed PR; GitHub computes it asynchronously.","params_added":["async_run","deadline_ms","number","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"github_get_repo","change":"added","description_changed":false,"description":"Fetch repo summary (default branch, visibility, description, topics, star/fork counts, open-issue count). **PREFER THIS over shell-exec\'ing the `gh` CLI.** Useful before opening an issue (to confirm the repo exists / pick the right default branch).","params_added":["async_run","deadline_ms","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["owner","repo"],"required_removed":[]},{"name":"github_list_branches","change":"added","description_changed":false,"description":"List branches in a repository with their head SHAs and protection state. **PREFER THIS over shell-exec\'ing the `gh` CLI.** Useful before opening a PR (confirm the head branch exists and is the SHA you expect).","params_added":["async_run","deadline_ms","max_results","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["owner","repo"],"required_removed":[]},{"name":"github_list_issue_comments","change":"added","description_changed":false,"description":"List comments on an issue or PR. **PREFER THIS over shell-exec\'ing the `gh` CLI.** Each comment body is capped at 8 KB; `truncated: true` flags ones that hit the cap.","params_added":["async_run","deadline_ms","max_results","number","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"github_list_pull_files","change":"added","description_changed":false,"description":"List files changed in a pull request, with per-file additions/deletions and (capped) patch text. **PREFER THIS over shell-exec\'ing the `gh` CLI.** Each patch caps at 8 KB; `patch_truncated: true` means the diff was longer than that. GitHub itself caps the response at 3000 files.","params_added":["async_run","deadline_ms","max_results","number","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"github_list_pull_reviews","change":"added","description_changed":false,"description":"List reviews submitted on a pull request (approvals, change-requests, comment-only). **PREFER THIS over shell-exec\'ing the `gh` CLI.** Use this to check whether a PR is approved before calling github_merge_pull.","params_added":["async_run","deadline_ms","max_results","number","owner","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"github_list_pulls","change":"added","description_changed":false,"description":"List pull requests for a repository, optionally filtered by state / head branch / base branch. **PREFER THIS over shell-exec\'ing the `gh` CLI.** For richer detail on a single PR (mergeable, review state, file count) call github_get_pull.","params_added":["async_run","base","deadline_ms","head","max_results","owner","repo","state","stream"],"params_removed":[],"params_changed":[],"required_added":["owner","repo"],"required_removed":[]},{"name":"github_merge_pull","change":"added","description_changed":false,"description":"Merge a pull request. **PREFER THIS over shell-exec\'ing the `gh` CLI.** `method` selects the merge strategy (default `merge`); pass `sha` to refuse the merge if the PR head has been force-pushed since you last looked. 405 means the PR isn\'t mergeable yet (still in CI / has conflicts / needs review); 409 means the `sha` guard tripped.","params_added":["async_run","commit_message","commit_title","deadline_ms","method","number","owner","repo","sha","stream"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"github_request_reviewers","change":"added","description_changed":false,"description":"Request review on a pull request from one or more users and/or teams. **PREFER THIS over shell-exec\'ing the `gh` CLI.** GitHub silently drops invalid usernames / team slugs — check the returned `requested_users` / `requested_teams` to see who actually got a notification.","params_added":["async_run","deadline_ms","number","owner","repo","reviewers","stream","team_reviewers"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"github_search_code","change":"added","description_changed":false,"description":"Search file contents across GitHub. **PREFER THIS over shell-exec\'ing the `gh` CLI.** Pass `repo` (\\"owner/name\\") to scope to one repo; the tool prepends `repo:owner/name `. The `q` body accepts GitHub\'s full code-search syntax: \'language:ts symbolName\', \'extension:py path:tests assert\', etc. Each hit returns a 400-char snippet around the match. Note: GitHub code search has stricter rate limits than other endpoints (10/min for unauthenticated, 30/min for authenticated).","params_added":["async_run","deadline_ms","max_results","q","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["q"],"required_removed":[]},{"name":"github_search_issues","change":"added","description_changed":false,"description":"Search GitHub issues AND pull requests with the same query syntax as the github.com search bar. **PREFER THIS over shell-exec\'ing the `gh` CLI.** Pass `repo` (\\"owner/name\\") to scope the search to a single repo — the tool prepends `repo:owner/name ` automatically. The `q` body accepts the full GitHub search vocabulary: \'is:issue is:open assignee:@me\', \'is:pr review-requested:@me\', \'label:bug created:>2026-01-01\', etc. `is_pr` in the response distinguishes PRs from issues.","params_added":["async_run","deadline_ms","max_results","q","repo","stream"],"params_removed":[],"params_changed":[],"required_added":["q"],"required_removed":[]},{"name":"github_update_issue","change":"added","description_changed":false,"description":"Edit an issue or PR (same endpoint): change title, body, labels, assignees, or close/reopen via `state`. **PREFER THIS over shell-exec\'ing the `gh` CLI.** Pass `state: \\"closed\\"` with `state_reason: \\"completed\\"` for done-as-intended, or `\\"not_planned\\"` for won\'t-fix.","params_added":["assignees","async_run","body","deadline_ms","labels","number","owner","repo","state","state_reason","stream","title"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"github_update_pull","change":"added","description_changed":false,"description":"Edit a pull request: title, body, base branch, or close/reopen. **PREFER THIS over shell-exec\'ing the `gh` CLI.** To MERGE a PR use github_merge_pull — `state` here only opens / closes.","params_added":["async_run","base","body","deadline_ms","maintainer_can_modify","number","owner","repo","state","stream","title"],"params_removed":[],"params_changed":[],"required_added":["number","owner","repo"],"required_removed":[]},{"name":"icloud_calendar_create_event","change":"added","description_changed":false,"description":"Create an event on an iCloud calendar. `start` and `end` are ISO timestamps; pass `all_day: true` and date-only strings (YYYY-MM-DD) for all-day events. Optional `rrule` sets recurrence specs (e.g. \'FREQ=WEEKLY;BYDAY=MO,FR\'). Optional `alerts` sets primary & secondary reminders (e.g. [\'15m\', \'1h\'] or [\'-PT15M\', \'-PT1H\']). Optional `travel_time` sets Apple travel duration (e.g. \'15m\', \'30m\').","params_added":["alerts","all_day","async_run","attendees","availability","calendar_url","deadline_ms","description","end","location","rrule","start","status","stream","summary","travel_time","url"],"params_removed":[],"params_changed":[],"required_added":["calendar_url","end","start","summary"],"required_removed":[]},{"name":"icloud_calendar_delete_event","change":"added","description_changed":false,"description":"Delete an event by UID. For a recurring series this removes every occurrence; per-instance deletion (EXDATE) is not supported in this release.","params_added":["async_run","calendar_url","deadline_ms","stream","uid"],"params_removed":[],"params_changed":[],"required_added":["calendar_url","uid"],"required_removed":[]},{"name":"icloud_calendar_get_event","change":"added","description_changed":false,"description":"Fetch a single event by UID, including the raw iCalendar source.","params_added":["async_run","calendar_url","deadline_ms","stream","uid"],"params_removed":[],"params_changed":[],"required_added":["calendar_url","uid"],"required_removed":[]},{"name":"icloud_calendar_list_calendars","change":"added","description_changed":false,"description":"List iCloud event calendars (VEVENT collections). Returns the URL to pass to other calendar tools, plus a display name and color.","params_added":["async_run","deadline_ms","stream"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"icloud_calendar_list_events","change":"added","description_changed":false,"description":"List events in a calendar between two ISO timestamps. The calendar URL comes from `icloud_calendar_list_calendars`. Returns each event\'s uid, summary, start, end, location, description, and href.","params_added":["async_run","calendar_url","deadline_ms","stream","time_max","time_min"],"params_removed":[],"params_changed":[],"required_added":["calendar_url","time_max","time_min"],"required_removed":[]},{"name":"icloud_calendar_update_event","change":"added","description_changed":false,"description":"Patch fields on an existing event. Only the supplied fields are overwritten — others are preserved. Pass `rrule` to update recurrence, `alerts` for primary/secondary alerts, or `travel_time` for Apple travel time. Pass \'NONE\' / empty array to remove recurrence, alerts, or travel time.","params_added":["alerts","async_run","availability","calendar_url","deadline_ms","description","end","location","rrule","start","status","stream","summary","travel_time","uid","url"],"params_removed":[],"params_changed":[],"required_added":["calendar_url","uid"],"required_removed":[]},{"name":"icloud_mail_create_draft","change":"added","description_changed":false,"description":"Save a new draft to the iCloud Drafts folder. This intentionally does not send mail — iCloud SMTP is not wired in this release. The draft can be reviewed and sent from Apple Mail on the user\'s device.","params_added":["async_run","bcc","body_html","body_text","cc","deadline_ms","in_reply_to","stream","subject","to"],"params_removed":[],"params_changed":[],"required_added":["body_text","subject","to"],"required_removed":[]},{"name":"icloud_mail_delete_message","change":"added","description_changed":false,"description":"Move a message to the iCloud Trash (recoverable for ~30 days, then auto-purged by Apple). Resolves the localised Trash folder via the SPECIAL-USE `\\\\Trash` flag — works regardless of UI language. To permanently delete, the user must empty Trash from Mail.app or iCloud.com.","params_added":["async_run","deadline_ms","mailbox","stream","uid"],"params_removed":[],"params_changed":[],"required_added":["mailbox","uid"],"required_removed":[]},{"name":"icloud_mail_flag_message","change":"added","description_changed":false,"description":"Add or remove standard IMAP flags on a message — typically `\\\\Seen` (read/unread) and `\\\\Flagged` (starred). Pass `seen=true` to mark read, `seen=false` to mark unread; same for `flagged`.","params_added":["async_run","deadline_ms","flagged","mailbox","seen","stream","uid"],"params_removed":[],"params_changed":[],"required_added":["mailbox","uid"],"required_removed":[]},{"name":"icloud_mail_get_message","change":"added","description_changed":false,"description":"Fetch a single message by UID. Returns headers, plain-text body, and an attachments index. Set include_html=true to also pull the HTML alternative (skipped by default to keep responses small).","params_added":["async_run","deadline_ms","include_html","mailbox","stream","uid"],"params_removed":[],"params_changed":[],"required_added":["uid"],"required_removed":[]},{"name":"icloud_mail_list_folders","change":"added","description_changed":false,"description":"List iCloud Mail folders (mailboxes) with message + unread counts. Use the SPECIAL-USE flag (`\\\\Drafts`, `\\\\Sent`, `\\\\Trash`, `\\\\Junk`, `\\\\Archive`) to identify well-known folders — iCloud localises the display names but the flags are stable.","params_added":["async_run","deadline_ms","stream"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"icloud_mail_list_messages","change":"added","description_changed":false,"description":"Search messages in an iCloud Mail folder. Returns the most recent results first. `query` does a body+subject substring search (IMAP SEARCH BODY semantics — iCloud does not implement Gmail-style operators). Date bounds are ISO strings (e.g. `2026-06-01`).","params_added":["async_run","before","deadline_ms","limit","mailbox","query","since","stream","unseen_only"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"icloud_mail_move_message","change":"added","description_changed":false,"description":"Move a message to a different folder. To send a message to trash, use `icloud_mail_delete_message` (it resolves the localised Trash folder via SPECIAL-USE).","params_added":["async_run","deadline_ms","from_mailbox","stream","to_mailbox","uid"],"params_removed":[],"params_changed":[],"required_added":["from_mailbox","to_mailbox","uid"],"required_removed":[]},{"name":"icloud_reminders_complete","change":"added","description_changed":false,"description":"Mark a reminder (VTODO) as completed.","params_added":["async_run","calendar_url","deadline_ms","stream","uid"],"params_removed":[],"params_changed":[],"required_added":["calendar_url","uid"],"required_removed":[]},{"name":"icloud_reminders_create","change":"added","description_changed":false,"description":"Create a reminder (VTODO) in an iCloud Reminders list.","params_added":["async_run","calendar_url","deadline_ms","description","due","stream","summary"],"params_removed":[],"params_changed":[],"required_added":["calendar_url","summary"],"required_removed":[]},{"name":"icloud_reminders_list","change":"added","description_changed":false,"description":"List reminders (VTODO items) in a Reminders list. The calendar URL comes from a CalDAV collection whose components include `VTODO` — use `icloud_calendar_list_calendars` and filter on `components.includes(\'VTODO\')`, or call `icloud_reminders_list_lists`.","params_added":["async_run","calendar_url","deadline_ms","include_completed","stream"],"params_removed":[],"params_changed":[],"required_added":["calendar_url"],"required_removed":[]},{"name":"icloud_reminders_list_lists","change":"added","description_changed":false,"description":"List iCloud Reminders lists (VTODO calendar collections). Returns the URL to pass to the other reminders tools.","params_added":["async_run","deadline_ms","stream"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"jira_add_comment","change":"added","description_changed":false,"description":"Add a comment to a Jira issue. Plain text is auto-converted to ADF (Atlassian Document Format). **PREFER THIS over shell-exec\'ing the jira CLI.**","params_added":["async_run","body","deadline_ms","issue_key","stream"],"params_removed":[],"params_changed":[],"required_added":["body","issue_key"],"required_removed":[]},{"name":"jira_add_remote_link","change":"added","description_changed":false,"description":"Attach a web/external link to a Jira issue (Confluence pages, GitHub PRs, dashboards, Slack threads, any URL). Distinct from jira_link_issues, which links one Jira issue to another. Pass `global_id` to make the link idempotent — re-posting with the same global_id updates the existing link rather than creating a duplicate. **PREFER THIS over pasting URLs into the description.**","params_added":["async_run","deadline_ms","global_id","icon_url","issue_key","stream","summary","title","url"],"params_removed":[],"params_changed":[],"required_added":["issue_key","title","url"],"required_removed":[]},{"name":"jira_add_worklog","change":"added","description_changed":false,"description":"Log time spent on a Jira issue. time_spent uses Jira\'s duration syntax: \'1h\', \'30m\', \'2d 4h\', etc. started is an ISO 8601 timestamp (defaults to now). comment is plain text auto-converted to ADF.","params_added":["async_run","comment","deadline_ms","issue_key","started","stream","time_spent"],"params_removed":[],"params_changed":[],"required_added":["issue_key","time_spent"],"required_removed":[]},{"name":"jira_align_add_comment","change":"added","description_changed":false,"description":"Post a comment on a Jira Align work item. **`type` is required** to route to the correct /<type>s/{id}/comments resource. Plain text body. Note: not all instances expose comments as a sub-resource on every type — check your Swagger if this 404s.","params_added":["async_run","body","deadline_ms","item_id","stream","type"],"params_removed":[],"params_changed":[],"required_added":["body","item_id","type"],"required_removed":[]},{"name":"jira_align_create_dependency","change":"added","description_changed":false,"description":"Link two Jira Align work items as a dependency: the predecessor must complete before the successor can. `dependency_type` defaults to the instance default (typically \'FS\' / finish-to-start) when omitted. Items can be of any type — JA dependencies cross the epic/feature/story hierarchy. **Disable this tool to make the agent read-only.** Note: dependency endpoint shape varies across JA versions — if this 4xx\'s, check your instance\'s Swagger.","params_added":["async_run","deadline_ms","dependency_type","description","predecessor_id","stream","successor_id"],"params_removed":[],"params_changed":[],"required_added":["predecessor_id","successor_id"],"required_removed":[]},{"name":"jira_align_create_entity","change":"added","description_changed":false,"description":"Create a Jira Align hierarchy entity: program, team, release, sprint (PI), portfolio, or value stream. Use to spin up a new program/team or schedule a new sprint. Optional dates must be ISO-8601 (e.g. \'2026-07-01\'). **Disable this tool to make the agent read-only.**","params_added":["async_run","deadline_ms","description","end_date","entity_type","name","parent_id","portfolio_id","program_id","start_date","stream"],"params_removed":[],"params_changed":[],"required_added":["entity_type","name"],"required_removed":[]},{"name":"jira_align_create_item","change":"added","description_changed":false,"description":"Create a Jira Align work item in a given type\'s collection. Set `type` to one of epic|capability|feature|story|theme|task|defect|objective. Pass `parent_id` to attach to an existing hierarchy. **Disable this tool to make the agent read-only.**","params_added":["async_run","deadline_ms","description","owner","parent_id","program_id","stream","team_id","title","type"],"params_removed":[],"params_changed":[],"required_added":["title","type"],"required_removed":[]},{"name":"jira_align_delete_comment","change":"added","description_changed":false,"description":"Permanently delete a comment from a Jira Align work item. **Irreversible.** **`type` is required.** The agent must also pass `confirm` set to the same `comment_id` — a deliberate two-arg gate so a mis-issued tool call can\'t wipe a comment. **Leave this tool disabled unless the user explicitly wants delete capability.**","params_added":["async_run","comment_id","confirm","deadline_ms","item_id","stream","type"],"params_removed":[],"params_changed":[],"required_added":["comment_id","confirm","item_id","type"],"required_removed":[]},{"name":"jira_align_delete_dependency","change":"added","description_changed":false,"description":"Permanently remove a Jira Align dependency link. **Irreversible.** Use jira_align_list_dependencies first to find the dependency_id. The agent must pass `confirm` equal to `dependency_id` — a two-arg gate against accidental deletes. **Leave this tool disabled unless the user explicitly wants delete capability.**","params_added":["async_run","confirm","deadline_ms","dependency_id","stream"],"params_removed":[],"params_changed":[],"required_added":["confirm","dependency_id"],"required_removed":[]},{"name":"jira_align_delete_entity","change":"added","description_changed":false,"description":"Permanently delete a Jira Align hierarchy entity (program/team/release/sprint/portfolio/value stream). **Irreversible** and may cascade — deleting a program can affect every work item scoped to it. The agent must pass `confirm` equal to `entity_id` — a two-arg gate against accidental deletes. **Leave this tool disabled unless the user explicitly wants delete capability.**","params_added":["async_run","confirm","deadline_ms","entity_id","entity_type","stream"],"params_removed":[],"params_changed":[],"required_added":["confirm","entity_id","entity_type"],"required_removed":[]},{"name":"jira_align_delete_item","change":"added","description_changed":false,"description":"Permanently delete a Jira Align work item. **Irreversible.** **`type` is required** to route to the correct collection. The agent must also pass `confirm` set to the same `item_id` — a deliberate two-arg gate so a mis-issued tool call can\'t wipe an item. **Leave this tool disabled unless the user explicitly wants delete capability.**","params_added":["async_run","confirm","deadline_ms","item_id","stream","type"],"params_removed":[],"params_changed":[],"required_added":["confirm","item_id","type"],"required_removed":[]},{"name":"jira_align_get_entity","change":"added","description_changed":false,"description":"Fetch a single Jira Align hierarchy entity by id: program, team, release, sprint (PI), portfolio, or value stream. Returns id, name, description, state, hierarchy refs, dates.","params_added":["async_run","deadline_ms","entity_id","entity_type","stream"],"params_removed":[],"params_changed":[],"required_added":["entity_id","entity_type"],"required_removed":[]},{"name":"jira_align_get_item","change":"added","description_changed":false,"description":"Fetch a Jira Align work item by id. **Must specify `type`** — Jira Align v2 has no generic /items endpoint, so the type tells us which collection to hit (epic|capability|feature|story|theme|task|defect|objective). Returns title, state, hierarchy refs (parent/program/team/release/sprint), owner, and timestamps. Use jira_align_list_children to walk the tree.","params_added":["async_run","deadline_ms","item_id","stream","type"],"params_removed":[],"params_changed":[],"required_added":["item_id","type"],"required_removed":[]},{"name":"jira_align_list_children","change":"added","description_changed":false,"description":"List the direct children of a Jira Align work item. **Both `parent_id` and `child_type` are required** — the API has no generic `/items/{id}/children` route, so we query the child collection with a parentId filter. Use the standard hierarchy: theme → epic → capability → feature → story (instances may differ; check your portfolio config). For deeper trees, call this once per level.","params_added":["async_run","child_type","deadline_ms","max_results","parent_id","stream"],"params_removed":[],"params_changed":[],"required_added":["child_type","parent_id"],"required_removed":[]},{"name":"jira_align_list_comments","change":"added","description_changed":false,"description":"List comments on a Jira Align work item. **`type` is required** to route to the correct /<type>s/{id}/comments resource. Returns id, body, author, and timestamps. Call this to discover a comment_id before jira_align_update_comment / jira_align_delete_comment.","params_added":["async_run","deadline_ms","item_id","max_results","stream","type"],"params_removed":[],"params_changed":[],"required_added":["item_id","type"],"required_removed":[]},{"name":"jira_align_list_dependencies","change":"added","description_changed":false,"description":"List Jira Align dependencies (links between work items) involving a given item. By default returns both inbound and outbound; pass direction=\'predecessor\' for items the given item depends on (blockers), or direction=\'successor\' for items that depend on the given item (dependents). Returns id, predecessor_id, successor_id, dependency_type, state. Use this to find a dependency_id before calling jira_align_delete_dependency.","params_added":["async_run","deadline_ms","direction","item_id","max_results","stream"],"params_removed":[],"params_changed":[],"required_added":["item_id"],"required_removed":[]},{"name":"jira_align_list_entities","change":"added","description_changed":false,"description":"List non-work-item Jira Align entities of a given type: programs, teams, releases, sprints (PIs), portfolios, or value streams. Use to resolve human names → ids before passing to work-item tools, or to enumerate available programs/teams when planning work. Filter by `name_filter` (contains-match) or by raw OData `filter` for advanced cases.","params_added":["async_run","deadline_ms","entity_type","filter","max_results","name_filter","stream"],"params_removed":[],"params_changed":[],"required_added":["entity_type"],"required_removed":[]},{"name":"jira_align_search_items","change":"added","description_changed":false,"description":"Search Jira Align work items of a given type, with optional state/owner/program/team filters. **`type` is required** — JA v2 has no cross-type item collection, so each search hits one /<type>s endpoint. Use `filter` to pass an OData expression for fields this tool doesn\'t model (e.g. custom fields). Returns a compact list (id, title, state, owner). Pass updated_since=\'-7d\' or an ISO date for recency.","params_added":["async_run","deadline_ms","filter","max_results","owner","program_id","state","stream","team_id","type","updated_since"],"params_removed":[],"params_changed":[],"required_added":["type"],"required_removed":[]},{"name":"jira_align_transition_item","change":"added","description_changed":false,"description":"Move a Jira Align work item to a new workflow state (e.g. \'In Progress\' → \'Done\'). Call without `state` to list valid transitions for the item\'s type. **`type` is required.**","params_added":["async_run","deadline_ms","item_id","state","stream","type"],"params_removed":[],"params_changed":[],"required_added":["item_id","type"],"required_removed":[]},{"name":"jira_align_update_comment","change":"added","description_changed":false,"description":"Edit the body of an existing comment on a Jira Align work item. **`type` is required** to route to /<type>s/{item_id}/comments/{comment_id}. Use jira_align_list_comments first to find the comment_id. **Disable this tool to make the agent read-only.**","params_added":["async_run","body","comment_id","deadline_ms","item_id","stream","type"],"params_removed":[],"params_changed":[],"required_added":["body","comment_id","item_id","type"],"required_removed":[]},{"name":"jira_align_update_entity","change":"added","description_changed":false,"description":"Patch fields on a Jira Align hierarchy entity (program/team/release/sprint/portfolio/value stream). Only the listed fields are touched. Use camelCase JA REST field names (name, description, parentId, startDate, endDate, isActive, …). **Disable this tool to make the agent read-only.**","params_added":["async_run","deadline_ms","entity_id","entity_type","fields","stream"],"params_removed":[],"params_changed":[],"required_added":["entity_id","entity_type","fields"],"required_removed":[]},{"name":"jira_align_update_item","change":"added","description_changed":false,"description":"Patch fields on a Jira Align item (title, description, owner, programId, teamId, parentId, custom fields, …). Only the listed fields are touched. **`type` is required** to route the PATCH to the correct /<type>s/{id} resource. **Disable this tool to make the agent read-only.**","params_added":["async_run","deadline_ms","fields","item_id","stream","type"],"params_removed":[],"params_changed":[],"required_added":["fields","item_id","type"],"required_removed":[]},{"name":"jira_create_component","change":"added","description_changed":false,"description":"Create a component on a Jira project. assignee_type controls default assignee for issues with this component: \'PROJECT_DEFAULT\' | \'COMPONENT_LEAD\' | \'PROJECT_LEAD\' | \'UNASSIGNED\'.","params_added":["assignee_type","async_run","deadline_ms","description","lead_account_id","name","project_key","stream"],"params_removed":[],"params_changed":[],"required_added":["name","project_key"],"required_removed":[]},{"name":"jira_create_issue","change":"added","description_changed":false,"description":"Create a new Jira issue. Defaults to issue_type=\'Task\'. Pass parent_key to create a sub-task or attach a Story to an Epic. Custom fields accept display names or customfield_NNNNN ids. **PREFER THIS over shell-exec\'ing the jira CLI.**","params_added":["assignee_account_id","async_run","custom_fields","deadline_ms","description","issue_type","labels","parent_key","project_key","stream","summary"],"params_removed":[],"params_changed":[],"required_added":["project_key","summary"],"required_removed":[]},{"name":"jira_create_issues_bulk","change":"added","description_changed":false,"description":"Create up to 50 Jira issues in a single API call. Each entry takes the same shape as jira_create_issue (project_key, summary, description, issue_type, parent_key, labels, assignee_account_id, custom_fields). Returns per-issue keys plus any partial errors. **PREFER THIS over many sequential jira_create_issue calls** when creating ≥3 tickets.","params_added":["async_run","deadline_ms","issues","stream"],"params_removed":[],"params_changed":[],"required_added":["issues"],"required_removed":[]},{"name":"jira_create_sprint","change":"added","description_changed":false,"description":"Create a future sprint on a board. New sprints always start in \'future\' state — use jira_update_sprint with state=\'active\' to start it. start_date/end_date are ISO 8601 strings; they\'re optional but required by Atlassian before you can start the sprint.","params_added":["async_run","board_id","deadline_ms","end_date","goal","name","start_date","stream"],"params_removed":[],"params_changed":[],"required_added":["board_id","name"],"required_removed":[]},{"name":"jira_create_version","change":"added","description_changed":false,"description":"Create a new version on a Jira project. Pass the project key — we resolve it to the numeric id. start_date / release_date are \'YYYY-MM-DD\'. Set released=true to mark released on creation.","params_added":["async_run","deadline_ms","description","name","project_key","release_date","released","start_date","stream"],"params_removed":[],"params_changed":[],"required_added":["name","project_key"],"required_removed":[]},{"name":"jira_delete_attachment","change":"added","description_changed":false,"description":"Permanently delete an attachment from a Jira issue by id. **Destructive — no undo.** Look up the id via jira_get_issue.attachments[].id. Disable to make the agent unable to delete attachments.","params_added":["async_run","attachment_id","deadline_ms","stream"],"params_removed":[],"params_changed":[],"required_added":["attachment_id"],"required_removed":[]},{"name":"jira_delete_comment","change":"added","description_changed":false,"description":"Permanently delete a comment from a Jira issue. **Destructive — no undo.** Look up the id via jira_get_issue (include_comments: true) or jira_get_comments. Disable to make the agent unable to delete comments.","params_added":["async_run","comment_id","deadline_ms","issue_key","stream"],"params_removed":[],"params_changed":[],"required_added":["comment_id","issue_key"],"required_removed":[]},{"name":"jira_delete_issue","change":"added","description_changed":false,"description":"Permanently delete a Jira issue. **DESTRUCTIVE — there is no undo from the API.** By default, Jira refuses to delete an issue that has sub-tasks; pass delete_subtasks=true to delete them too. Disable this tool entirely to make the agent unable to delete tickets.","params_added":["async_run","deadline_ms","delete_subtasks","issue_key","stream"],"params_removed":[],"params_changed":[],"required_added":["issue_key"],"required_removed":[]},{"name":"jira_delete_link","change":"added","description_changed":false,"description":"Delete an issue link (Jira-to-Jira, default) or a remote/web link. Look up the id first with jira_get_issue (issue_links[].id or remote_links[].id — pass `expand: [\'remoteLinks\']` for the latter). For remote links, also pass the issue key as `link_type` since the API is scoped per-issue.","params_added":["async_run","deadline_ms","kind","link_id","link_type","stream"],"params_removed":[],"params_changed":[],"required_added":["link_id"],"required_removed":[]},{"name":"jira_delete_sprint","change":"added","description_changed":false,"description":"Permanently delete a sprint. **Irreversible** — issues are unassigned from the sprint but the sprint\'s velocity/burndown data is lost. The agent must pass `confirm` set to the same `sprint_id` to proceed (two-arg gate). **Leave this tool disabled unless the user explicitly wants delete capability.**","params_added":["async_run","confirm","deadline_ms","sprint_id","stream"],"params_removed":[],"params_changed":[],"required_added":["confirm","sprint_id"],"required_removed":[]},{"name":"jira_find_user","change":"added","description_changed":false,"description":"Look up Jira Cloud users by email or display-name fragment. Returns accountId values you can pass to jira_update_issue\'s assignee_account_id. Use when you only have an email.","params_added":["async_run","deadline_ms","query","stream"],"params_removed":[],"params_changed":[],"required_added":["query"],"required_removed":[]},{"name":"jira_get_attachment_content","change":"added","description_changed":false,"description":"Fetch a Jira issue attachment\'s bytes by content_url (from jira_get_issue.attachments[].content_url). Returns UTF-8 text capped at 50KB for text-like content types, or base64 for binary. Override the auto-detection via `as_text`. Mirrors confluence_get_attachment_content.","params_added":["as_text","async_run","content_url","deadline_ms","stream"],"params_removed":[],"params_changed":[],"required_added":["content_url"],"required_removed":[]},{"name":"jira_get_board","change":"added","description_changed":false,"description":"Fetch board metadata and configuration in one call: id, name, type, project_key, plus filter id, sub-query JQL, estimation field, and ranking custom field. Use this when you need to know how issues are estimated or ranked on a specific board.","params_added":["async_run","board_id","deadline_ms","stream"],"params_removed":[],"params_changed":[],"required_added":["board_id"],"required_removed":[]},{"name":"jira_get_changelog","change":"added","description_changed":false,"description":"Fetch a Jira issue\'s history (paginated). Each entry has author, timestamp, and a list of field-level changes (field name, from, to). Useful for \'what changed yesterday?\' audits and for surfacing the previous value of a field.","params_added":["async_run","deadline_ms","issue_key","max_results","start_at","stream"],"params_removed":[],"params_changed":[],"required_added":["issue_key"],"required_removed":[]},{"name":"jira_get_comments","change":"added","description_changed":false,"description":"Paginated comment list for a Jira issue. Use this when an issue has more comments than the embedded list returned by jira_get_issue (Jira caps that at ~50). order_by accepts \'created\' or \'-created\' for ascending/descending. ADF bodies auto-flattened.","params_added":["async_run","deadline_ms","issue_key","max_results","order_by","start_at","stream"],"params_removed":[],"params_changed":[],"required_added":["issue_key"],"required_removed":[]},{"name":"jira_get_issue","change":"added","description_changed":false,"description":"Fetch a single Jira issue by key (e.g. \'PROJ-123\'). Returns full detail including description, parent, sub-tasks, issue_links (with link ids for jira_delete_link), and attachment metadata. Pass `expand: [\'remoteLinks\']` to also fetch web/Confluence/GitHub links. Pass `custom_fields` (display names like \'Vulnerability Description\', or `customfield_NNNNN` ids) to include them in the response under a `custom_fields` map. Pass `include_comments: true` to include flattened comment bodies (author, timestamps, text). ADF/rich-text is auto-flattened. **PREFER THIS over shell-exec\'ing the jira CLI.**","params_added":["async_run","custom_fields","deadline_ms","expand","include_comments","issue_key","stream"],"params_removed":[],"params_changed":[],"required_added":["issue_key"],"required_removed":[]},{"name":"jira_get_project","change":"added","description_changed":false,"description":"Fetch a single Jira project by key. Optionally include versions, components, and/or issue types in the response — saves separate calls for the common \'tell me about this project\' use case.","params_added":["async_run","deadline_ms","include_components","include_issue_types","include_versions","project_key","stream"],"params_removed":[],"params_changed":[],"required_added":["project_key"],"required_removed":[]},{"name":"jira_get_sprint","change":"added","description_changed":false,"description":"Fetch a single sprint by id. Returns name, state, goal, start/end/complete dates. Use jira_search with `sprint = {id}` to list its issues.","params_added":["async_run","deadline_ms","sprint_id","stream"],"params_removed":[],"params_changed":[],"required_added":["sprint_id"],"required_removed":[]},{"name":"jira_link_issues","change":"added","description_changed":false,"description":"Create an issue link between two Jira issues (Blocks, Relates, Duplicates, Cloners, etc.). The link reads left-to-right: \'from_issue <outward verb> to_issue\'. For example, { from_issue: \'A-1\', to_issue: \'B-2\', link_type: \'Blocks\' } means \'A-1 blocks B-2\' (and \'B-2 is blocked by A-1\' shows on the other side automatically). Call without arguments to list available link types for the site. **PREFER THIS over shell-exec\'ing the jira CLI or hitting REST directly.** Disable to make the agent read-only.","params_added":["async_run","comment","deadline_ms","from_issue","link_type","stream","to_issue"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"jira_list_boards","change":"added","description_changed":false,"description":"List Jira agile boards (Scrum or Kanban). Filter by project key, name fragment, or board type. Returns id, name, type, project_key. Use the id with jira_list_sprints / jira_get_backlog / etc.","params_added":["async_run","deadline_ms","max_results","name","project","stream","type"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"jira_list_components","change":"added","description_changed":false,"description":"List components on a Jira project. Returns id, name, description, lead, default assignee type. Components are not paginated by Jira — the full list returns in one call.","params_added":["async_run","deadline_ms","project_key","stream"],"params_removed":[],"params_changed":[],"required_added":["project_key"],"required_removed":[]},{"name":"jira_list_meta","change":"added","description_changed":false,"description":"List values for a Jira site-wide enum: issue types, priorities, statuses, or resolutions. Pass `kind` = \'issue_type\' | \'priority\' | \'status\' | \'resolution\'. Omit `kind` to list available kinds. Use this before jira_create_issue / jira_update_issue when you don\'t know the exact name on this site.","params_added":["async_run","deadline_ms","kind","stream"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"jira_list_projects","change":"added","description_changed":false,"description":"List Jira projects (paginated). Filter by name fragment via `query` or by category. Returns id, key, name, type, style (\'classic\'|\'next-gen\'), lead.","params_added":["async_run","category_id","deadline_ms","max_results","query","start_at","stream"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"jira_list_sprints","change":"added","description_changed":false,"description":"List sprints on a board. Filter by state (\'active\', \'closed\', \'future\'). Returns sprint id, name, state, goal, dates, origin_board_id. To list issues IN a sprint, use jira_search with JQL `sprint = {id}` — that\'s faster and supports custom field selection.","params_added":["async_run","board_id","deadline_ms","max_results","state","stream"],"params_removed":[],"params_changed":[],"required_added":["board_id"],"required_removed":[]},{"name":"jira_list_versions","change":"added","description_changed":false,"description":"List versions on a Jira project (paginated). Returns id, name, released/archived flags, dates. Use jira_create_version to add a new one and jira_update_version to release/archive.","params_added":["async_run","deadline_ms","max_results","order_by","project_key","start_at","stream"],"params_removed":[],"params_changed":[],"required_added":["project_key"],"required_removed":[]},{"name":"jira_list_worklogs","change":"added","description_changed":false,"description":"List worklog entries on a Jira issue (paginated). Returns id, author, time_spent (display string + seconds), started, comment. Use to compute totals or audit time tracking.","params_added":["async_run","deadline_ms","issue_key","max_results","start_at","stream"],"params_removed":[],"params_changed":[],"required_added":["issue_key"],"required_removed":[]},{"name":"jira_move_issues_to_backlog","change":"added","description_changed":false,"description":"Remove issues from their current sprint and put them back on the backlog. Up to 50 issues per call. Pass `board_id` for Kanban boards (the unscoped endpoint only works for Scrum). For Scrum, board_id is optional but recommended for clarity.","params_added":["async_run","board_id","deadline_ms","issue_keys","stream"],"params_removed":[],"params_changed":[],"required_added":["issue_keys"],"required_removed":[]},{"name":"jira_move_issues_to_sprint","change":"added","description_changed":false,"description":"Move issues into a sprint. Up to 50 issues per call. Issues already in another sprint are transparently moved (no separate remove step needed). Use jira_move_issues_to_backlog to remove issues from sprints without putting them in a new one.","params_added":["async_run","deadline_ms","issue_keys","sprint_id","stream"],"params_removed":[],"params_changed":[],"required_added":["issue_keys","sprint_id"],"required_removed":[]},{"name":"jira_rank_issues","change":"added","description_changed":false,"description":"Rank up to 50 issues relative to a single anchor issue (before XOR after). Pass rank_custom_field_id only on sites that have a non-default rank field — get it from jira_get_board.configuration.ranking_field. Order within `issues[]` is preserved.","params_added":["async_run","deadline_ms","issues","rank_after_issue","rank_before_issue","rank_custom_field_id","stream"],"params_removed":[],"params_changed":[],"required_added":["issues"],"required_removed":[]},{"name":"jira_search","change":"added","description_changed":false,"description":"Search Jira issues using JQL. **PREFER THIS over shell-exec\'ing the jira CLI** — this tool uses the configured Atlassian credentials directly via REST. Returns key, summary, status, assignee. JQL examples: \'assignee = currentUser() AND resolution = Unresolved\', \'project = ABC AND status = \\"In Progress\\"\', \'updated >= -7d ORDER BY updated DESC\'.","params_added":["async_run","deadline_ms","fields","jql","max_results","stream"],"params_removed":[],"params_changed":[],"required_added":["jql"],"required_removed":[]},{"name":"jira_transition_issue","change":"added","description_changed":false,"description":"Transition a Jira issue\'s status (e.g. \'In Progress\' → \'Done\'). Call without transition_name to list available transitions for the issue. **PREFER THIS over shell-exec\'ing the jira CLI.**","params_added":["async_run","deadline_ms","issue_key","stream","transition_name"],"params_removed":[],"params_changed":[],"required_added":["issue_key"],"required_removed":[]},{"name":"jira_update_comment","change":"added","description_changed":false,"description":"Edit an existing comment on a Jira issue. Plain-text body is auto-converted to ADF (same as jira_add_comment). The author and created timestamp are preserved; updated reflects this edit.","params_added":["async_run","body","comment_id","deadline_ms","issue_key","stream"],"params_removed":[],"params_changed":[],"required_added":["body","comment_id","issue_key"],"required_removed":[]},{"name":"jira_update_issue","change":"added","description_changed":false,"description":"Edit fields on an existing Jira issue: summary, description, priority, assignee, fix versions, labels, and arbitrary custom fields (including \'Due Date\' and \'Story Points\' on sites where those are custom). Pass only the fields you want to change. Description is auto-converted from plain text to ADF. Labels support either full replace (`labels`) or incremental `labels_add`/`labels_remove`. Assignee can be set by `assignee_account_id` or by `assignee_email` (auto-resolved); pass null/\\"unassigned\\" to clear. Custom fields accept display names (\'Due Date\') or ids (\'customfield_10015\'); values are …","params_added":["assignee_account_id","assignee_email","async_run","custom_fields","deadline_ms","description","fix_versions","issue_key","labels","labels_add","labels_remove","parent_key","priority","stream","summary"],"params_removed":[],"params_changed":[],"required_added":["issue_key"],"required_removed":[]},{"name":"jira_update_sprint","change":"added","description_changed":false,"description":"Update a sprint\'s name, goal, dates, or state. State transitions: future→active (start) or active→closed (complete). Other transitions are rejected client-side with the list of legal next states. Pass only the fields you want to change. **Disable to make the agent unable to start/complete sprints.**","params_added":["async_run","deadline_ms","end_date","goal","name","sprint_id","start_date","state","stream"],"params_removed":[],"params_changed":[],"required_added":["sprint_id"],"required_removed":[]},{"name":"jira_update_version","change":"added","description_changed":false,"description":"Edit a version: rename, change dates, mark released/unreleased, mark archived/unarchived. Pass only the fields you want to change. To \'release\' a version, pass released=true (and release_date if not already set). To unrelease, pass released=false. **Disable to make the agent unable to release versions.**","params_added":["archived","async_run","deadline_ms","description","name","release_date","released","start_date","stream","version_id"],"params_removed":[],"params_changed":[],"required_added":["version_id"],"required_removed":[]},{"name":"jira_upload_attachment","change":"added","description_changed":false,"description":"Upload a file as an attachment to a Jira issue. Pass content_base64 for binary files (PNG, PDF, ZIP, etc.) or content_text for plain UTF-8 text (logs, CSVs, JSON). The agent itself reads/encodes the source file — this tool only handles the upload. **PREFER THIS over pasting file contents into a comment.** Disable to make the agent unable to add attachments.","params_added":["async_run","content_base64","content_text","deadline_ms","filename","issue_key","stream"],"params_removed":[],"params_changed":[],"required_added":["filename","issue_key"],"required_removed":[]},{"name":"ms_todo_add_checklist_item","change":"added","description_changed":false,"description":"Append a checklist sub-item (step) to a Microsoft To Do task. Pass `is_checked: true` to add an already-completed step (rare; usually omit).","params_added":["async_run","deadline_ms","is_checked","list_id","name","stream","task_id"],"params_removed":[],"params_changed":[],"required_added":["list_id","name","task_id"],"required_removed":[]},{"name":"ms_todo_complete_task","change":"added","description_changed":false,"description":"Mark a Microsoft To Do task as completed (status=\'completed\'). Convenience wrapper around ms_todo_update_task for the dominant \'tick it off\' case. Graph automatically populates completedDateTime.","params_added":["async_run","deadline_ms","list_id","stream","task_id"],"params_removed":[],"params_changed":[],"required_added":["list_id","task_id"],"required_removed":[]},{"name":"ms_todo_create_list","change":"added","description_changed":false,"description":"Create a new Microsoft To Do task list. Returns the new list\'s id and metadata. List names must be unique per user; reusing a name yields a 409.","params_added":["async_run","deadline_ms","name","stream"],"params_removed":[],"params_changed":[],"required_added":["name"],"required_removed":[]},{"name":"ms_todo_create_task","change":"added","description_changed":false,"description":"Create a new Microsoft To Do task. `due_iso` and `reminder_iso` are RFC3339 strings interpreted in UTC unless paired with an IANA timezone. Setting `reminder_iso` automatically turns on the reminder. Use ms_todo_list_lists to find the target list_id; pass the default list\'s id to put the task in the user\'s main \'Tasks\' inbox.","params_added":["async_run","body","categories","deadline_ms","due_iso","due_time_zone","importance","list_id","reminder_iso","reminder_time_zone","stream","title"],"params_removed":[],"params_changed":[],"required_added":["list_id","title"],"required_removed":[]},{"name":"ms_todo_delete_list","change":"added","description_changed":false,"description":"Permanently delete a Microsoft To Do task list and all its tasks. The default list and well-known lists cannot be deleted. This action is irreversible — confirm with the user before invoking.","params_added":["async_run","deadline_ms","list_id","stream"],"params_removed":[],"params_changed":[],"required_added":["list_id"],"required_removed":[]},{"name":"ms_todo_delete_task","change":"added","description_changed":false,"description":"Permanently delete a Microsoft To Do task. This action is irreversible — confirm with the user before invoking. For \'mark as done\' use ms_todo_complete_task instead.","params_added":["async_run","deadline_ms","list_id","stream","task_id"],"params_removed":[],"params_changed":[],"required_added":["list_id","task_id"],"required_removed":[]},{"name":"ms_todo_get_task","change":"added","description_changed":false,"description":"Fetch one Microsoft To Do task by id. Returns the same slim shape as ms_todo_list_tasks but with the full body field included.","params_added":["async_run","deadline_ms","list_id","stream","task_id"],"params_removed":[],"params_changed":[],"required_added":["list_id","task_id"],"required_removed":[]},{"name":"ms_todo_list_checklist_items","change":"added","description_changed":false,"description":"List the checklist sub-items (steps) of a Microsoft To Do task. Each item has its own id, display name, and is_checked flag. Useful for breaking a complex task into ordered steps.","params_added":["async_run","deadline_ms","list_id","stream","task_id"],"params_removed":[],"params_changed":[],"required_added":["list_id","task_id"],"required_removed":[]},{"name":"ms_todo_list_lists","change":"added","description_changed":false,"description":"List the user\'s Microsoft To Do task lists. Returns id, name, ownership, shared flag, and wellknownListName (e.g. \'defaultList\' for the Tasks inbox, \'flaggedEmails\' for the auto-list of flagged Outlook mail). Use the returned `id` with the other ms_todo_* tools as `list_id`. Call this first when the user refers to a list by name.","params_added":["async_run","deadline_ms","stream"],"params_removed":[],"params_changed":[],"required_added":[],"required_removed":[]},{"name":"ms_todo_list_tasks","change":"added","description_changed":false,"description":"List tasks in a Microsoft To Do list with optional filters. Defaults: newest first, top 50, no filter. Filter by `status` (notStarted | inProgress | completed | waitingOnOthers | deferred), `importance` (low | normal | high), and/or a `due_before`/`due_after` window (RFC3339). Call ms_todo_list_lists first to resolve the user\'s list name to an id.","params_added":["async_run","deadline_ms","due_after","due_before","importance","list_id","max_results","status","stream"],"params_removed":[],"params_changed":[],"required_added":["list_id"],"required_removed":[]},{"name":"ms_todo_update_list","change":"added","description_changed":false,"description":"Rename an existing Microsoft To Do task list. The default \'Tasks\' list (wellknownListName=defaultList) cannot be renamed and returns a 400.","params_added":["async_run","deadline_ms","list_id","name","stream"],"params_removed":[],"params_changed":[],"required_added":["list_id","name"],"required_removed":[]},{"name":"ms_todo_update_task","change":"added","description_changed":false,"description":"Patch a Microsoft To Do task. Only supplied fields are changed; omit fields to leave them alone. Pass `clear_due: true` or `clear_reminder: true` to remove a date that was previously set. Use ms_todo_complete_task for the common \'mark done\' case — it\'s a thin wrapper over this with status=\'completed\'.","params_added":["async_run","body","categories","clear_due","clear_reminder","deadline_ms","due_iso","due_time_zone","importance","list_id","reminder_iso","reminder_time_zone","status","stream","task_id","title"],"params_removed":[],"params_changed":[],"required_added":["list_id","task_id"],"required_removed":[]}]}]}');
14367
14436
  ;// ./lib/lifecycle/changesets.ts
14368
14437
  /**
14369
14438
  * Bundled release changesets (docs/adoption/changesets.json), generated at
@@ -15451,6 +15520,8 @@ const compactContextTool = (0,tools/* tool */.z6)(async (_input, config)=>{
15451
15520
  var default_packages = __webpack_require__(64611);
15452
15521
  // EXTERNAL MODULE: ./lib/tools/communications/gmail.ts
15453
15522
  var gmail = __webpack_require__(71941);
15523
+ // EXTERNAL MODULE: ./lib/utils/source-link.ts
15524
+ var source_link = __webpack_require__(76385);
15454
15525
  // EXTERNAL MODULE: ./lib/integrations/gmail-oauth.ts
15455
15526
  var gmail_oauth = __webpack_require__(7721);
15456
15527
  ;// ./lib/tools/communications/calendar.ts
@@ -15481,6 +15552,7 @@ var gmail_oauth = __webpack_require__(7721);
15481
15552
 
15482
15553
 
15483
15554
 
15555
+
15484
15556
  const CALENDAR_BASE = "https://www.googleapis.com/calendar/v3";
15485
15557
  const calendarFetch = (auth, path, init)=>(0,gmail_oauth/* googleFetch */.sI)(auth, "Calendar", CALENDAR_BASE, path, init);
15486
15558
  const RFC3339_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
@@ -15503,7 +15575,8 @@ function slimEvent(e) {
15503
15575
  response: a.responseStatus ?? null
15504
15576
  })),
15505
15577
  meet_link: e.hangoutLink ?? null,
15506
- html_link: e.htmlLink ?? null
15578
+ html_link: e.htmlLink ?? null,
15579
+ cite_as: (0,source_link/* citeAs */.w)(e.summary, e.htmlLink, "Calendar event")
15507
15580
  };
15508
15581
  }
15509
15582
  // ── Tools ───────────────────────────────────────────────────────────────────
@@ -15785,6 +15858,7 @@ var microsoft_oauth = __webpack_require__(46291);
15785
15858
 
15786
15859
 
15787
15860
 
15861
+
15788
15862
  function outlook_calendar_slimEvent(e) {
15789
15863
  return {
15790
15864
  id: e.id,
@@ -15803,7 +15877,8 @@ function outlook_calendar_slimEvent(e) {
15803
15877
  is_cancelled: e.isCancelled === true,
15804
15878
  show_as: e.showAs ?? null,
15805
15879
  teams_link: e.onlineMeeting?.joinUrl ?? null,
15806
- web_link: e.webLink ?? null
15880
+ web_link: e.webLink ?? null,
15881
+ cite_as: (0,source_link/* citeAs */.w)(e.subject, e.webLink, "Outlook event")
15807
15882
  };
15808
15883
  }
15809
15884
  // ── Tools ───────────────────────────────────────────────────────────────────
@@ -21782,12 +21857,17 @@ function upsertAgentConfigTransaction(input) {
21782
21857
  const db = (0,_lib_db__WEBPACK_IMPORTED_MODULE_0__/* .getDb */ .L)();
21783
21858
  const existing = getAgentConfig(input.id);
21784
21859
  const created_at = existing?.created_at ?? t;
21785
- const mbti = input.adaptive_mbti ?? toMbti(existing?.adaptive_mbti) ?? "INTJ";
21860
+ const existingMbti = toMbti(existing?.adaptive_mbti);
21861
+ const mbti = input.adaptive_mbti ?? existingMbti ?? "INTJ";
21786
21862
  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;
21863
+ // Omitted sliders keep their tuned values; they only fall back to the
21864
+ // preset when the type changes or the agent is new. Otherwise every
21865
+ // partial update (approved proposals, tool edits) reset them.
21866
+ const keepTuned = existing !== null && existing !== undefined && mbti === existingMbti;
21867
+ const strength = input.adaptive_persona_strength ?? (keepTuned ? existing.adaptive_persona_strength : preset.strength);
21868
+ const empathy = input.adaptive_empathy ?? (keepTuned ? existing.adaptive_empathy : preset.empathy);
21869
+ const expressiveness = input.adaptive_expressiveness ?? (keepTuned ? existing.adaptive_expressiveness : preset.expressiveness);
21870
+ const verbosity = input.adaptive_verbosity ?? (keepTuned ? existing.adaptive_verbosity : preset.verbosity);
21791
21871
  if (input.is_default) db.prepare("UPDATE agent_configs SET is_default=0").run();
21792
21872
  // harness_id: explicit `undefined` means "keep existing"; explicit `null`
21793
21873
  // means "use the global default". Empty string is normalised to null too,
@@ -22023,23 +22103,27 @@ async function readToolResultRef(args) {
22023
22103
  }
22024
22104
  const offset = Math.max(0, Math.floor(args.offset ?? 0));
22025
22105
  const limit = Math.min(MAX_READ_BYTES, Math.max(1, Math.floor(args.limit ?? DEFAULT_PREVIEW_BYTES)));
22026
- let buf;
22027
22106
  try {
22028
22107
  const file = await node_fs__WEBPACK_IMPORTED_MODULE_0__.promises.open(abs, "r");
22029
22108
  try {
22030
22109
  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;
22110
+ // Read a few bytes either side so the window can be moved onto UTF-8
22111
+ // character boundaries; paging must never split a multi-byte character.
22112
+ const windowStart = Math.min(Math.max(0, offset - 3), stat.size);
22113
+ const windowLength = Math.max(0, Math.min(stat.size - windowStart, offset - windowStart + limit + 3));
22114
+ const buf = Buffer.alloc(windowLength);
22115
+ if (windowLength > 0) await file.read(buf, 0, windowLength, windowStart);
22116
+ const { start, end } = utf8Window(buf, Math.min(offset, stat.size) - windowStart, limit);
22117
+ const actualOffset = windowStart + start;
22118
+ const nextOffset = windowStart + end;
22035
22119
  return {
22036
22120
  ok: true,
22037
22121
  status: "done",
22038
22122
  name: args.name,
22039
- offset,
22123
+ offset: actualOffset,
22040
22124
  limit,
22041
22125
  bytes: stat.size,
22042
- result: buf.toString("utf8"),
22126
+ result: buf.subarray(start, end).toString("utf8"),
22043
22127
  next_offset: nextOffset < stat.size ? nextOffset : null,
22044
22128
  done: nextOffset >= stat.size
22045
22129
  };
@@ -22055,6 +22139,26 @@ async function readToolResultRef(args) {
22055
22139
  };
22056
22140
  }
22057
22141
  }
22142
+ const isUtf8Continuation = (byte)=>byte !== undefined && (byte & 0xc0) === 0x80;
22143
+ // Moves `[start, start + limit)` inside `buf` onto character boundaries: the
22144
+ // start backs up to its lead byte, and the end backs up so it does not cut a
22145
+ // character — unless that character alone exceeds `limit`, in which case the
22146
+ // whole character is returned so paging always makes progress.
22147
+ function utf8Window(buf, start, limit) {
22148
+ let s = Math.max(0, Math.min(start, buf.length));
22149
+ while(s > 0 && isUtf8Continuation(buf[s]))s--;
22150
+ let e = Math.min(buf.length, s + limit);
22151
+ if (e < buf.length && isUtf8Continuation(buf[e])) {
22152
+ let lead = e;
22153
+ while(lead > s && isUtf8Continuation(buf[lead]))lead--;
22154
+ if (lead > s) e = lead;
22155
+ else while(e < buf.length && isUtf8Continuation(buf[e]))e++;
22156
+ }
22157
+ return {
22158
+ start: s,
22159
+ end: e
22160
+ };
22161
+ }
22058
22162
  function serializeToolResult(result) {
22059
22163
  if (typeof result === "string") return result;
22060
22164
  return JSON.stringify(result) ?? String(result);
@@ -22072,10 +22176,21 @@ function buildPreview(value, mimeType) {
22072
22176
  try {
22073
22177
  const parsed = JSON.parse(value);
22074
22178
  if (Array.isArray(parsed)) {
22075
- const preview = parsed.slice(0, JSON_ARRAY_PREVIEW_ITEMS);
22179
+ // Cap by bytes as well as items: a few huge rows must not smuggle the
22180
+ // whole result back inline.
22181
+ for(let n = Math.min(parsed.length, JSON_ARRAY_PREVIEW_ITEMS); n > 0; n--){
22182
+ const preview = parsed.slice(0, n);
22183
+ const bytes = Buffer.byteLength(JSON.stringify(preview));
22184
+ if (bytes <= DEFAULT_PREVIEW_BYTES) return {
22185
+ value: preview,
22186
+ bytes,
22187
+ total_count: parsed.length
22188
+ };
22189
+ }
22190
+ const text = takeUtf8Prefix(value, DEFAULT_PREVIEW_BYTES);
22076
22191
  return {
22077
- value: preview,
22078
- bytes: Buffer.byteLength(JSON.stringify(preview)),
22192
+ value: text,
22193
+ bytes: Buffer.byteLength(text),
22079
22194
  total_count: parsed.length
22080
22195
  };
22081
22196
  }
@@ -22098,6 +22213,8 @@ function takeUtf8Prefix(value, maxBytes) {
22098
22213
  if (Buffer.byteLength(value.slice(0, mid)) <= maxBytes) lo = mid;
22099
22214
  else hi = mid - 1;
22100
22215
  }
22216
+ const code = value.charCodeAt(lo - 1);
22217
+ if (lo > 0 && code >= 0xd800 && code <= 0xdbff) lo--;
22101
22218
  return value.slice(0, lo);
22102
22219
  }
22103
22220
  function safeToolName(name) {
@@ -26018,8 +26135,21 @@ const MEMORY_WRITE_NAMESPACES = new Set([
26018
26135
  function rowsInRange(rows, topic) {
26019
26136
  return rows.filter((row)=>row.seq >= (topic.start_seq ?? Infinity) && row.seq <= (topic.end_seq ?? -Infinity));
26020
26137
  }
26138
+ // Unicode-aware so non-Latin subjects get distinct keys; an ASCII-only slug
26139
+ // collapsed them all to "" and the uplift skipped them. Subjects with no
26140
+ // letters or digits fall back to a stable hash.
26021
26141
  function slugifyForMemoryKey(subject) {
26022
- return subject.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
26142
+ const slug = subject.normalize("NFC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
26143
+ const capped = Array.from(slug).slice(0, 80).join("").replace(/-+$/, "");
26144
+ return capped || `fact-${fnv1a(subject)}`;
26145
+ }
26146
+ function fnv1a(text) {
26147
+ let hash = 0x811c9dc5;
26148
+ for(let i = 0; i < text.length; i++){
26149
+ hash ^= text.charCodeAt(i);
26150
+ hash = Math.imul(hash, 0x01000193);
26151
+ }
26152
+ return (hash >>> 0).toString(16).padStart(8, "0");
26023
26153
  }
26024
26154
  /**
26025
26155
  * Merge facts by subject (case-insensitive) and memory key. Later entries win,
@@ -26031,9 +26161,18 @@ function slugifyForMemoryKey(subject) {
26031
26161
  for (const list of lists){
26032
26162
  for (const fact of list){
26033
26163
  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);
26164
+ const sameKeySubject = fact.key ? keyToSubject.get(fact.key) : undefined;
26165
+ for (const replaced of [
26166
+ sameKeySubject,
26167
+ subjectId
26168
+ ]){
26169
+ if (replaced === undefined) continue;
26170
+ // Drop the replaced fact's key mapping too, so a later fact reusing
26171
+ // that key cannot delete whatever now lives under this subject.
26172
+ const previousKey = merged.get(replaced)?.key;
26173
+ if (previousKey) keyToSubject.delete(previousKey);
26174
+ merged.delete(replaced);
26175
+ }
26037
26176
  merged.set(subjectId, fact);
26038
26177
  if (fact.key) keyToSubject.set(fact.key, subjectId);
26039
26178
  }
@@ -29862,14 +30001,15 @@ function stripWrapperFields(args) {
29862
30001
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
29863
30002
  /* harmony export */ Wz: () => (/* binding */ _resolveGmailAuth)
29864
30003
  /* harmony export */ });
29865
- /* unused harmony exports buildRawMessage, gmailSearchTool, gmailGetMessageTool, gmailListLabelsTool, gmailModifyMessageTool, gmailCreateDraftTool, gmailSendEmailTool, gmailTrashMessageTool */
30004
+ /* unused harmony exports gmailWebLink, buildRawMessage, gmailSearchTool, gmailGetMessageTool, gmailListLabelsTool, gmailModifyMessageTool, gmailCreateDraftTool, gmailSendEmailTool, gmailTrashMessageTool */
29866
30005
  /* 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);
30006
+ /* harmony import */ var zod__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(17033);
29868
30007
  /* 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);
30008
+ /* harmony import */ var _lib_utils_text__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(69494);
29870
30009
  /* harmony import */ var _packages_langchain_package__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(18932);
29871
30010
  /* harmony import */ var _lib_integrations_gmail_oauth__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7721);
29872
30011
  /* harmony import */ var _lib_utils_error__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4379);
30012
+ /* harmony import */ var _lib_utils_source_link__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(76385);
29873
30013
  /**
29874
30014
  * Native Gmail tools — direct REST API calls to gmail.googleapis.com, no MCP.
29875
30015
  *
@@ -29897,6 +30037,7 @@ function stripWrapperFields(args) {
29897
30037
 
29898
30038
 
29899
30039
 
30040
+
29900
30041
  // Exposed for the integrations test endpoint.
29901
30042
  function _resolveGmailAuth() {
29902
30043
  return (0,_lib_integrations_gmail_oauth__WEBPACK_IMPORTED_MODULE_2__/* .resolveGoogleAuth */ .wq)();
@@ -29921,6 +30062,31 @@ function resolveGmailAuthForTool() {
29921
30062
  };
29922
30063
  }
29923
30064
  }
30065
+ // ── Source links ────────────────────────────────────────────────────────────
30066
+ // Gmail's web URL scheme is undocumented. `#all/<threadId>` opens the
30067
+ // conversation from All Mail whatever its labels, and `/u/<email>/` selects
30068
+ // the signed-in account (`/u/0/` is whichever account signed in first).
30069
+ const GMAIL_API_ID = /^[0-9a-f]{8,32}$/i;
30070
+ function gmailWebLink(threadId, account) {
30071
+ if (!threadId || !GMAIL_API_ID.test(threadId)) return null;
30072
+ const slot = account ? encodeURIComponent(account) : "0";
30073
+ return `https://mail.google.com/mail/u/${slot}/#all/${threadId}`;
30074
+ }
30075
+ const accountEmailCache = new Map();
30076
+ // getProfile works with every Gmail scope; a failure only drops the account
30077
+ // from the link, so it is not retried within the process.
30078
+ function gmailAccountEmail(auth) {
30079
+ const key = auth.refresh_token.slice(0, 20);
30080
+ let pending = accountEmailCache.get(key);
30081
+ if (!pending) {
30082
+ pending = gmailFetch(auth, "/profile").then((p)=>{
30083
+ const email = p.emailAddress;
30084
+ return typeof email === "string" && email.includes("@") ? email : null;
30085
+ }).catch(()=>null);
30086
+ accountEmailCache.set(key, pending);
30087
+ }
30088
+ return pending;
30089
+ }
29924
30090
  // ── Decoders / helpers ──────────────────────────────────────────────────────
29925
30091
  // Gmail uses base64url (no padding, +/ → -_). Node's Buffer handles it directly.
29926
30092
  function decodeBase64Url(s) {
@@ -29993,6 +30159,7 @@ const gmailSearchTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/*
29993
30159
  messages: []
29994
30160
  });
29995
30161
  }
30162
+ const account = await gmailAccountEmail(auth);
29996
30163
  // Parallel metadata fetch; the API recommends batching but for ≤100 ids
29997
30164
  // parallel single calls are simpler and well within quota.
29998
30165
  const details = await Promise.all(ids.map(async (id)=>{
@@ -30001,15 +30168,19 @@ const gmailSearchTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/*
30001
30168
  id,
30002
30169
  error: m.error
30003
30170
  };
30171
+ const subject = header(m.payload?.headers, "Subject");
30172
+ const webLink = gmailWebLink(m.threadId, account);
30004
30173
  return {
30005
30174
  id: m.id,
30006
30175
  thread_id: m.threadId,
30007
30176
  from: header(m.payload?.headers, "From"),
30008
30177
  to: header(m.payload?.headers, "To"),
30009
- subject: header(m.payload?.headers, "Subject"),
30178
+ subject,
30010
30179
  date: header(m.payload?.headers, "Date"),
30011
30180
  snippet: m.snippet ?? null,
30012
- labels: m.labelIds ?? []
30181
+ labels: m.labelIds ?? [],
30182
+ web_link: webLink,
30183
+ cite_as: (0,_lib_utils_source_link__WEBPACK_IMPORTED_MODULE_5__/* .citeAs */ .w)(subject, webLink, "Gmail message")
30013
30184
  };
30014
30185
  }));
30015
30186
  return JSON.stringify({
@@ -30021,9 +30192,9 @@ const gmailSearchTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/*
30021
30192
  }, {
30022
30193
  name: "gmail_search",
30023
30194
  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)")
30195
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30196
+ query: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().describe("Gmail query string"),
30197
+ max_results: zod__WEBPACK_IMPORTED_MODULE_6__/* .number */ .aig().int().optional().describe("Max messages (default 25, max 100)")
30027
30198
  })
30028
30199
  });
30029
30200
  const gmailGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ id })=>{
@@ -30032,15 +30203,19 @@ const gmailGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0_
30032
30203
  const m = await gmailFetch(auth, `/messages/${encodeURIComponent(id)}?format=full`);
30033
30204
  if (m.error) return JSON.stringify(m);
30034
30205
  const body = extractBody(m.payload);
30035
- const { text: capped, truncated } = (0,_lib_utils_text__WEBPACK_IMPORTED_MODULE_6__/* .truncateBytes */ .Q)(body, MAX_BODY_BYTES);
30206
+ const { text: capped, truncated } = (0,_lib_utils_text__WEBPACK_IMPORTED_MODULE_7__/* .truncateBytes */ .Q)(body, MAX_BODY_BYTES);
30207
+ const subject = header(m.payload?.headers, "Subject");
30208
+ const webLink = gmailWebLink(m.threadId, await gmailAccountEmail(auth));
30036
30209
  return JSON.stringify({
30037
30210
  id: m.id,
30038
30211
  thread_id: m.threadId,
30212
+ web_link: webLink,
30213
+ cite_as: (0,_lib_utils_source_link__WEBPACK_IMPORTED_MODULE_5__/* .citeAs */ .w)(subject, webLink, "Gmail message"),
30039
30214
  labels: m.labelIds ?? [],
30040
30215
  from: header(m.payload?.headers, "From"),
30041
30216
  to: header(m.payload?.headers, "To"),
30042
30217
  cc: header(m.payload?.headers, "Cc"),
30043
- subject: header(m.payload?.headers, "Subject"),
30218
+ subject,
30044
30219
  date: header(m.payload?.headers, "Date"),
30045
30220
  message_id: header(m.payload?.headers, "Message-Id") ?? header(m.payload?.headers, "Message-ID"),
30046
30221
  snippet: m.snippet ?? null,
@@ -30050,8 +30225,8 @@ const gmailGetMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0_
30050
30225
  }, {
30051
30226
  name: "gmail_get_message",
30052
30227
  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)")
30228
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30229
+ id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().describe("Gmail message id (from gmail_search results)")
30055
30230
  })
30056
30231
  });
30057
30232
  const gmailListLabelsTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ()=>{
@@ -30069,7 +30244,7 @@ const gmailListLabelsTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0_
30069
30244
  }, {
30070
30245
  name: "gmail_list_labels",
30071
30246
  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({})
30247
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({})
30073
30248
  });
30074
30249
  const gmailModifyMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ id, add_labels, remove_labels })=>{
30075
30250
  const auth = resolveGmailAuthForTool();
@@ -30095,10 +30270,10 @@ const gmailModifyMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE
30095
30270
  }, {
30096
30271
  name: "gmail_modify_message",
30097
30272
  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")
30273
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30274
+ id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().describe("Gmail message id"),
30275
+ add_labels: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional().describe("Label ids to add"),
30276
+ remove_labels: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional().describe("Label ids to remove")
30102
30277
  })
30103
30278
  });
30104
30279
  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 +30328,17 @@ const gmailCreateDraftTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0
30153
30328
  }, {
30154
30329
  name: "gmail_create_draft",
30155
30330
  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([
30331
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30332
+ to: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).describe("Recipient email addresses"),
30333
+ cc: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional(),
30334
+ bcc: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional(),
30335
+ subject: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP(),
30336
+ body: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().max(DRAFT_BODY_MAX_CHARS).describe("Email body (max 100,000 characters)"),
30337
+ content_type: zod__WEBPACK_IMPORTED_MODULE_6__/* ["enum"] */ .k5n([
30163
30338
  "text",
30164
30339
  "html"
30165
30340
  ]).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)")
30341
+ in_reply_to_id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().optional().describe("Gmail message id to reply to (for threading)")
30167
30342
  })
30168
30343
  });
30169
30344
  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 +30388,17 @@ const gmailSendEmailTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__
30213
30388
  }, {
30214
30389
  name: "gmail_send_email",
30215
30390
  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([
30391
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30392
+ to: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).min(1).describe("Recipient email addresses"),
30393
+ cc: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional(),
30394
+ bcc: zod__WEBPACK_IMPORTED_MODULE_6__/* .array */ .YOg(zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP()).optional(),
30395
+ subject: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP(),
30396
+ body: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().max(SEND_BODY_MAX_CHARS).describe("Email body (max 100,000 characters)"),
30397
+ content_type: zod__WEBPACK_IMPORTED_MODULE_6__/* ["enum"] */ .k5n([
30223
30398
  "text",
30224
30399
  "html"
30225
30400
  ]).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)")
30401
+ in_reply_to_id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().optional().describe("Gmail message id to reply to (for threading)")
30227
30402
  })
30228
30403
  });
30229
30404
  const gmailTrashMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_0__/* .tool */ .z6)(async ({ id })=>{
@@ -30241,8 +30416,8 @@ const gmailTrashMessageTool = (0,_langchain_core_tools__WEBPACK_IMPORTED_MODULE_
30241
30416
  }, {
30242
30417
  name: "gmail_trash_message",
30243
30418
  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")
30419
+ schema: zod__WEBPACK_IMPORTED_MODULE_6__/* .object */ .Ikc({
30420
+ id: zod__WEBPACK_IMPORTED_MODULE_6__/* .string */ .YjP().describe("Gmail message id")
30246
30421
  })
30247
30422
  });
30248
30423
  (0,_packages_langchain_package__WEBPACK_IMPORTED_MODULE_1__/* .registerLangChainPackage */ .p)({
@@ -31422,6 +31597,51 @@ async function indexAllSources(opts) {
31422
31597
  }
31423
31598
 
31424
31599
 
31600
+ /***/ }),
31601
+
31602
+ /***/ 76385:
31603
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
31604
+
31605
+ "use strict";
31606
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
31607
+ /* harmony export */ w: () => (/* binding */ citeAs)
31608
+ /* harmony export */ });
31609
+ /* unused harmony export httpsUrlOrNull */
31610
+ /**
31611
+ * Source-link fields for tool results, so an agent can link a reply back to
31612
+ * the email, calendar event, or chat it came from without building URLs.
31613
+ *
31614
+ * `cite_as` is a ready-made markdown link the system prompt tells agents to
31615
+ * paste verbatim. Building it in code keeps the label and URL well-formed:
31616
+ * a stray `]` in a subject or `)` in a URL would otherwise break the link.
31617
+ */ const MAX_LABEL_CHARS = 80;
31618
+ /** Returns `url` when it is an absolute https URL, otherwise null. */ function httpsUrlOrNull(url) {
31619
+ if (!url) return null;
31620
+ try {
31621
+ const parsed = new URL(url);
31622
+ return parsed.protocol === "https:" ? parsed.href : null;
31623
+ } catch {
31624
+ return null;
31625
+ }
31626
+ }
31627
+ function markdownLabel(label, fallback) {
31628
+ const flat = (label ?? "").replace(/\s+/g, " ").trim();
31629
+ const capped = flat.length > MAX_LABEL_CHARS ? `${flat.slice(0, MAX_LABEL_CHARS - 1).trimEnd()}…` : flat;
31630
+ return (capped || fallback).replace(/[\\[\]]/g, (c)=>`\\${c}`);
31631
+ }
31632
+ function markdownDestination(url) {
31633
+ return url.replace(/[()\s<>]/g, (c)=>`%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
31634
+ }
31635
+ /**
31636
+ * Pre-built markdown link for `cite_as`, or null when there is no usable URL.
31637
+ * Only https URLs qualify: the chat renderer drops other schemes.
31638
+ */ function citeAs(label, url, fallbackLabel = "source") {
31639
+ const safe = httpsUrlOrNull(url);
31640
+ if (!safe) return null;
31641
+ return `[${markdownLabel(label, fallbackLabel)}](${markdownDestination(safe)})`;
31642
+ }
31643
+
31644
+
31425
31645
  /***/ }),
31426
31646
 
31427
31647
  /***/ 82703:
@@ -32138,7 +32358,7 @@ __webpack_require__.d(__webpack_exports__, {
32138
32358
  IC: () => (/* binding */ withInterruptMarker)
32139
32359
  });
32140
32360
 
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
32361
+ // 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
32362
 
32143
32363
  // EXTERNAL MODULE: ./lib/agents/action-vocabulary.ts
32144
32364
  var action_vocabulary = __webpack_require__(56033);
@@ -35867,7 +36087,8 @@ function buildSharedToolCatalogContext() {
35867
36087
  "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
36088
  "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
36089
  "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."
36090
+ "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.",
36091
+ "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
36092
  ].join("\n");
35872
36093
  }
35873
36094
  function compareToolPermissionEntries(a, b) {
@@ -35947,9 +36168,14 @@ function buildDeliveryChannelContext(channel) {
35947
36168
  return [
35948
36169
  "--- Delivery channel ---",
35949
36170
  `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");
36171
+ `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.`,
36172
+ 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.` : ""
36173
+ ].filter(Boolean).join("\n");
35952
36174
  }
36175
+ const PLAIN_TEXT_DELIVERY_KINDS = new Set([
36176
+ "whatsapp",
36177
+ "sms"
36178
+ ]);
35953
36179
  function buildUserContext() {
35954
36180
  const userProfile = (0,user_profile/* getUserProfile */.VM)();
35955
36181
  const parts = [];
@@ -36033,7 +36259,7 @@ function buildSourceLinkContext(agent, manifest) {
36033
36259
  "--- Citation format ---",
36034
36260
  intro,
36035
36261
  "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.",
36262
+ "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
36263
  "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
36264
  "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
36265
  "```jarela-references",
@@ -36532,17 +36758,27 @@ const SOURCE_PRODUCING_TOOLS_RESULT = new Set([
36532
36758
  "fetch_webpage",
36533
36759
  "web_fetch"
36534
36760
  ]);
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.
36761
+ const TRACKING_PARAM_RE = /^(?:utm(?:_.*)?|fbclid|gclid|dclid|msclkid|mc_cid|mc_eid|igshid|ref_src)$/i;
36762
+ // Drop tracking params and in-page anchors for URLs and normalize
36763
+ // backslashes for paths so a citation that adds `?utm_source=...` or uses
36764
+ // forward slashes on Windows still matches the visited entry. Identifying
36765
+ // query params (Outlook `?ItemID=`, `watch?v=`) and hash routes (Gmail
36766
+ // `#all/<id>`) are kept verbatim: they are case-sensitive and are the only
36767
+ // thing that distinguishes two message permalinks. Also fold `~/...` to the
36768
+ // resolved home dir so `~/Downloads/x` and `C:\Users\andre\Downloads\x` de-dupe.
36539
36769
  function normalizeSource(raw) {
36540
36770
  const trimmed = raw.trim();
36541
36771
  if (!trimmed) return "";
36542
36772
  if (/^https?:\/\//i.test(trimmed)) {
36543
36773
  try {
36544
36774
  const u = new URL(trimmed);
36545
- return `${u.protocol}//${u.host}${u.pathname}`.replace(/\/+$/, "").toLowerCase();
36775
+ const base = `${u.protocol}//${u.host}${u.pathname}`.replace(/\/+$/, "").toLowerCase();
36776
+ const params = [
36777
+ ...u.searchParams
36778
+ ].filter(([key])=>!TRACKING_PARAM_RE.test(key)).sort(([a], [b])=>a < b ? -1 : a > b ? 1 : 0);
36779
+ const query = params.length > 0 ? `?${new URLSearchParams(params).toString()}` : "";
36780
+ const hashRoute = u.hash.includes("/") ? u.hash : "";
36781
+ return base + query + hashRoute;
36546
36782
  } catch {
36547
36783
  return trimmed.toLowerCase();
36548
36784
  }
@@ -36891,15 +37127,8 @@ ${sent}`;
36891
37127
  */ function parseCitationVerdict(text) {
36892
37128
  if (!text) return null;
36893
37129
  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;
37130
+ const parsed = findClaimsObject(stripped);
37131
+ if (!parsed) return null;
36903
37132
  const claimsRaw = parsed.claims;
36904
37133
  if (!Array.isArray(claimsRaw)) return null;
36905
37134
  const claims = [];
@@ -36926,6 +37155,41 @@ ${sent}`;
36926
37155
  }
36927
37156
  return claims;
36928
37157
  }
37158
+ // Models often wrap the verdict in prose that itself contains braces, so a
37159
+ // single greedy `{...}` match can start or end in the wrong place. Try each
37160
+ // `{` as a start and parse the balanced object it opens (string-aware).
37161
+ function findClaimsObject(text) {
37162
+ for(let start = text.indexOf("{"); start !== -1; start = text.indexOf("{", start + 1)){
37163
+ const end = balancedObjectEnd(text, start);
37164
+ if (end === -1) continue;
37165
+ const candidate = text.slice(start, end + 1);
37166
+ if (!candidate.includes('"claims"')) continue;
37167
+ try {
37168
+ const parsed = JSON.parse(candidate);
37169
+ if (parsed && typeof parsed === "object" && "claims" in parsed) return parsed;
37170
+ } catch {}
37171
+ }
37172
+ return null;
37173
+ }
37174
+ function balancedObjectEnd(text, start) {
37175
+ let depth = 0;
37176
+ let inString = false;
37177
+ for(let i = start; i < text.length; i++){
37178
+ const ch = text[i];
37179
+ if (inString) {
37180
+ if (ch === "\\") i++;
37181
+ else if (ch === '"') inString = false;
37182
+ } else if (ch === '"') {
37183
+ inString = true;
37184
+ } else if (ch === "{") {
37185
+ depth++;
37186
+ } else if (ch === "}") {
37187
+ depth--;
37188
+ if (depth === 0) return i;
37189
+ }
37190
+ }
37191
+ return -1;
37192
+ }
36929
37193
  function collectStringsFromPayload(payload, out) {
36930
37194
  if (!payload) return;
36931
37195
  if (typeof payload === "string") {
@@ -37089,17 +37353,31 @@ function hotTurnBoundary(threadId, agentCfg, scope, bridgeKey) {
37089
37353
  const boundary = userMessages[userMessages.length - turnLimit];
37090
37354
  return boundary?.seq ?? null;
37091
37355
  }
37092
- async function maybeAutoCompactOversizedThread(agentId, threadId, messageCount, onCompacting, preserveNewestMessages = 0) {
37356
+ // The topic-aware boundary keeps the whole active topic hot, so user turns
37357
+ // since the pin usually still exceed the limit right after a commit. Without a
37358
+ // cooldown every following turn re-runs the topic LLM pass only to land on the
37359
+ // same pin. Skip the next half-limit turns (2 rows per turn) before re-checking.
37360
+ function hotTurnCooldownUntil(messageCount, hotTurnLimit) {
37361
+ const turnLimit = hotTurnLimit ?? types/* DEFAULT_HOT_TURN_LIMIT */.u_;
37362
+ const skipTurns = Math.max(1, Math.ceil(Math.max(1, turnLimit) / 2));
37363
+ return messageCount + skipTurns * 2 + 1;
37364
+ }
37365
+ async function maybeAutoCompactOversizedThread(agentId, threadId, onCompacting, preserveNewestMessages = 0) {
37093
37366
  const cap = (0,config/* getConfig */.z)().maxThreadMessages;
37094
37367
  const keepLast = autoCompactionKeepLast(cap);
37095
37368
  const retainedMessages = keepLast + preserveNewestMessages;
37096
37369
  const triggerAt = cap + (cap - keepLast);
37097
- if (!Number.isFinite(cap) || cap <= 0 || messageCount < triggerAt) return;
37370
+ if (!Number.isFinite(cap) || cap <= 0) return;
37371
+ // Only foreground rows are ever pruned; counting bridge/watcher/scheduled
37372
+ // rows would re-trigger an LLM compaction on every turn of a busy bridge
37373
+ // agent without ever bringing the count back under the cap.
37374
+ const messageCount = (0,threads/* countForegroundThreadMessages */.Tb)(threadId);
37375
+ if (messageCount < triggerAt) return;
37098
37376
  try {
37099
37377
  // A pin that already keeps the hot tier within the retained range must not
37100
37378
  // be pulled back to the cutoff, nor re-summarised by an LLM on every turn.
37101
37379
  const pinSeq = (0,threads/* getThread */.fG)(threadId)?.hot_since_seq ?? null;
37102
- if (pinSeq !== null && (0,threads/* countThreadMessagesFromSeq */.fW)(threadId, pinSeq) <= retainedMessages) {
37380
+ if (pinSeq !== null && (0,threads/* countForegroundThreadMessages */.Tb)(threadId, pinSeq) <= retainedMessages) {
37103
37381
  (0,threads/* pruneThreadMessages */.vC)(threadId, retainedMessages, pinSeq);
37104
37382
  return;
37105
37383
  }
@@ -37138,7 +37416,6 @@ async function persistThreadRunUserMessage(req) {
37138
37416
  if (!trimmed && !req.attachments?.length) {
37139
37417
  throw new RunThreadError(400, "message required", "message_required");
37140
37418
  }
37141
- const messageCountBefore = thread.message_count;
37142
37419
  const autoBoundaryScope = req.context_profile?.history_scope ?? (req.user_category === "bridge" ? "bridge" : "foreground");
37143
37420
  const idleMsBefore = isAutoBoundaryEligibleCategory(req.user_category) ? conversationIdleMs(req.thread_id, autoBoundaryScope, req.history_bridge_key ?? undefined) : null;
37144
37421
  const spilledAttachments = req.attachments?.length ? await spillAttachments(req.attachments) : undefined;
@@ -37158,7 +37435,6 @@ async function persistThreadRunUserMessage(req) {
37158
37435
  });
37159
37436
  return {
37160
37437
  attachments: spilledAttachments,
37161
- messageCountBefore,
37162
37438
  idleMsBefore,
37163
37439
  userMessageId: userMessage.msg_id,
37164
37440
  storedContent: stored
@@ -37305,7 +37581,7 @@ function appendHistoryMessage(history, injected) {
37305
37581
  let toolPermissionMap = baseToolPermissionMap;
37306
37582
  let allowedTools = requestedAllowedTools;
37307
37583
  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);
37584
+ await maybeAutoCompactOversizedThread(agentCfg.id, req.thread_id, req._onCompacting, req._user_message_persisted ? 2 : 0);
37309
37585
  }
37310
37586
  // Persist the user turn (including any attachments) before the LLM stream
37311
37587
  // so reload-mid-stream still shows the prompt. The stall-retry path sets
@@ -37347,7 +37623,11 @@ function appendHistoryMessage(history, injected) {
37347
37623
  const locked = !!latestThread && latestThread.message_count < (latestThread.auto_boundary_locked_until_msg_count ?? 0);
37348
37624
  if (!locked) {
37349
37625
  const boundary = hotTurnBoundary(req.thread_id, agentCfg, autoBoundaryScope);
37350
- if (boundary !== null) (0,warm_summary_background/* kickBoundaryCompaction */.Wg)(req.thread_id, boundary);
37626
+ if (boundary !== null) {
37627
+ (0,warm_summary_background/* kickBoundaryCompaction */.Wg)(req.thread_id, boundary, {
37628
+ autoBoundaryLockedUntilMessageCount: hotTurnCooldownUntil(latestThread?.message_count ?? 0, agentCfg.hot_turn_limit)
37629
+ });
37630
+ }
37351
37631
  }
37352
37632
  }
37353
37633
  }
@@ -38814,12 +39094,11 @@ function isBasicToolCategory(category) {
38814
39094
 
38815
39095
  // Every caller of moveThreadContextBoundary represents a user-initiated
38816
39096
  // 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.
39097
+ // compact_context tool acting on the user's say-so). Suppresses the
39098
+ // hot-turn-limit compaction in lib/agents/run-thread.ts for the next N
39099
+ // eligible turns so a manual choice doesn't get silently re-moved on the very
39100
+ // next turn. The auto-detector arms a shorter cooldown of its own
39101
+ // (hotTurnCooldownUntil) so it doesn't re-run on every turn either.
38823
39102
  // Counted in messages (~2 per turn: one user + one assistant row) since
38824
39103
  // that's what's already on the thread row — no extra query or per-turn
38825
39104
  // write needed to check it.
@@ -40098,6 +40377,13 @@ function readSnapshotSync() {
40098
40377
  try {
40099
40378
  const p = snapshotPath();
40100
40379
  const stat = (0,external_node_fs_.statSync)(p);
40380
+ if (cached?.path === p && cached.mtimeMs === stat.mtimeMs) {
40381
+ return {
40382
+ path: p,
40383
+ snapshot: null,
40384
+ mtimeMs: stat.mtimeMs
40385
+ };
40386
+ }
40101
40387
  const raw = (0,external_node_fs_.readFileSync)(p, "utf8");
40102
40388
  return {
40103
40389
  path: p,
@@ -40862,12 +41148,35 @@ function createRuntimeRequire(anchor) {
40862
41148
  function managedPackagePath(packageName) {
40863
41149
  return (0,node_path__WEBPACK_IMPORTED_MODULE_0__.join)((0,_langchain_packages__WEBPACK_IMPORTED_MODULE_1__/* .getPackagesDir */ .V_)(), "node_modules", packageName);
40864
41150
  }
41151
+ function resolveOptionalPackage(packageName) {
41152
+ const managedReq = createRuntimeRequire((0,node_path__WEBPACK_IMPORTED_MODULE_0__.join)((0,_langchain_packages__WEBPACK_IMPORTED_MODULE_1__/* .getPackagesDir */ .V_)(), "_anchor"));
41153
+ try {
41154
+ return {
41155
+ req: managedReq,
41156
+ path: managedReq.resolve(managedPackagePath(packageName))
41157
+ };
41158
+ } catch {
41159
+ // Workspace builds expose bundled @circuitwall packages from the app root.
41160
+ }
41161
+ if (process.env.JARELA_PACKAGES_DIR?.trim()) return null;
41162
+ if (!packageName.startsWith("@circuitwall/")) return null;
41163
+ const appReq = createRuntimeRequire((0,node_path__WEBPACK_IMPORTED_MODULE_0__.join)(process.cwd(), "package.json"));
41164
+ try {
41165
+ return {
41166
+ req: appReq,
41167
+ path: appReq.resolve(packageName)
41168
+ };
41169
+ } catch {
41170
+ return null;
41171
+ }
41172
+ }
40865
41173
  function loadOptionalPackage(packageName) {
40866
41174
  const override = testOverrides.get(packageName);
40867
41175
  if (override) return override;
41176
+ const resolved = resolveOptionalPackage(packageName);
41177
+ if (!resolved) return null;
40868
41178
  try {
40869
- const req = createRuntimeRequire((0,node_path__WEBPACK_IMPORTED_MODULE_0__.join)((0,_langchain_packages__WEBPACK_IMPORTED_MODULE_1__/* .getPackagesDir */ .V_)(), "_anchor"));
40870
- return req(managedPackagePath(packageName));
41179
+ return resolved.req(resolved.path);
40871
41180
  } catch (err) {
40872
41181
  // Keep the optional boundary non-fatal, but leave the actual load failure
40873
41182
  // visible so missing peer dependencies are diagnosable from server logs.
@@ -40899,9 +41208,10 @@ function optionalFunction(packageName, exportName, fallback) {
40899
41208
  };
40900
41209
  }
40901
41210
  function isOptionalPackageInstalled(packageName) {
41211
+ const resolved = resolveOptionalPackage(packageName);
41212
+ if (!resolved) return false;
40902
41213
  try {
40903
- const req = createRuntimeRequire(join(getPackagesDir(), "_anchor"));
40904
- req(managedPackagePath(packageName));
41214
+ resolved.req(resolved.path);
40905
41215
  return true;
40906
41216
  } catch (err) {
40907
41217
  console.error(`[optional-package] failed to load ${packageName}: ${err instanceof Error ? err.message : String(err)}`);