@agent-native/core 0.131.9 → 0.132.1

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 (295) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +105 -0
  3. package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
  4. package/corpus/core/docs/design/durable-agent-runs.md +15 -11
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/a2a/activity.ts +127 -10
  7. package/corpus/core/src/a2a/index.ts +3 -0
  8. package/corpus/core/src/action.ts +52 -11
  9. package/corpus/core/src/agent/durable-background.ts +38 -28
  10. package/corpus/core/src/agent/harness/background.ts +12 -2
  11. package/corpus/core/src/agent/production-agent.ts +210 -43
  12. package/corpus/core/src/agent/run-store.ts +45 -3
  13. package/corpus/core/src/application-state/handlers.ts +45 -0
  14. package/corpus/core/src/application-state/index.ts +3 -0
  15. package/corpus/core/src/application-state/store.ts +28 -11
  16. package/corpus/core/src/audit/redact.ts +104 -18
  17. package/corpus/core/src/client/AgentPanel.tsx +2 -7
  18. package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
  19. package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
  20. package/corpus/core/src/client/agent-chat.ts +4 -9
  21. package/corpus/core/src/client/application-state.ts +115 -12
  22. package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
  23. package/corpus/core/src/client/guided-questions.tsx +12 -23
  24. package/corpus/core/src/db/client.ts +148 -28
  25. package/corpus/core/src/db/create-get-db.ts +39 -6
  26. package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
  27. package/corpus/core/src/deploy/build.ts +26 -33
  28. package/corpus/core/src/feature-flags/store.ts +10 -5
  29. package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
  30. package/corpus/core/src/integrations/config-store.ts +13 -0
  31. package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
  32. package/corpus/core/src/mcp-client/routes.ts +33 -2
  33. package/corpus/core/src/onboarding/plugin.ts +31 -25
  34. package/corpus/core/src/org/accept-pending.ts +2 -0
  35. package/corpus/core/src/org/auto-join-domain.ts +2 -0
  36. package/corpus/core/src/org/context.ts +35 -10
  37. package/corpus/core/src/org/handlers.ts +6 -0
  38. package/corpus/core/src/org/request-org-cache.ts +71 -0
  39. package/corpus/core/src/resources/store.ts +10 -9
  40. package/corpus/core/src/scripts/runner.ts +6 -3
  41. package/corpus/core/src/secrets/storage.ts +97 -5
  42. package/corpus/core/src/server/action-change.ts +33 -0
  43. package/corpus/core/src/server/action-routes.ts +11 -9
  44. package/corpus/core/src/server/agent-teams.ts +10 -2
  45. package/corpus/core/src/server/better-auth-instance.ts +39 -12
  46. package/corpus/core/src/server/core-routes-plugin.ts +19 -2
  47. package/corpus/core/src/server/credential-provider.ts +57 -14
  48. package/corpus/core/src/server/http-response-telemetry.ts +1 -0
  49. package/corpus/core/src/server/poll.ts +95 -57
  50. package/corpus/core/src/server/voice-providers-status.ts +19 -4
  51. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
  52. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
  53. package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
  54. package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
  55. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
  56. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
  57. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
  58. package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
  59. package/corpus/templates/analytics/app/i18n-data.ts +40 -0
  60. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
  61. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
  62. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
  63. package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
  64. package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
  66. package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
  67. package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
  68. package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
  69. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
  70. package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
  71. package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
  72. package/corpus/templates/content/netlify.toml +3 -0
  73. package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
  74. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
  75. package/corpus/templates/slides/actions/patch-deck.ts +25 -1
  76. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
  77. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
  78. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  79. package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
  80. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
  81. package/corpus/templates/slides/app/pages/Index.tsx +2 -2
  82. package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
  83. package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
  84. package/corpus/toolkit/CHANGELOG.md +8 -0
  85. package/corpus/toolkit/package.json +1 -1
  86. package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
  87. package/dist/a2a/activity.d.ts +22 -4
  88. package/dist/a2a/activity.d.ts.map +1 -1
  89. package/dist/a2a/activity.js +97 -11
  90. package/dist/a2a/activity.js.map +1 -1
  91. package/dist/a2a/index.d.ts +1 -1
  92. package/dist/a2a/index.d.ts.map +1 -1
  93. package/dist/a2a/index.js +1 -1
  94. package/dist/a2a/index.js.map +1 -1
  95. package/dist/action.d.ts.map +1 -1
  96. package/dist/action.js +41 -11
  97. package/dist/action.js.map +1 -1
  98. package/dist/agent/durable-background.d.ts +13 -8
  99. package/dist/agent/durable-background.d.ts.map +1 -1
  100. package/dist/agent/durable-background.js +36 -28
  101. package/dist/agent/durable-background.js.map +1 -1
  102. package/dist/agent/harness/background.d.ts.map +1 -1
  103. package/dist/agent/harness/background.js +12 -2
  104. package/dist/agent/harness/background.js.map +1 -1
  105. package/dist/agent/production-agent.d.ts.map +1 -1
  106. package/dist/agent/production-agent.js +195 -44
  107. package/dist/agent/production-agent.js.map +1 -1
  108. package/dist/agent/run-store.d.ts +2 -0
  109. package/dist/agent/run-store.d.ts.map +1 -1
  110. package/dist/agent/run-store.js +44 -3
  111. package/dist/agent/run-store.js.map +1 -1
  112. package/dist/application-state/handlers.d.ts +15 -0
  113. package/dist/application-state/handlers.d.ts.map +1 -1
  114. package/dist/application-state/handlers.js +42 -2
  115. package/dist/application-state/handlers.js.map +1 -1
  116. package/dist/application-state/index.d.ts +2 -2
  117. package/dist/application-state/index.d.ts.map +1 -1
  118. package/dist/application-state/index.js +2 -2
  119. package/dist/application-state/index.js.map +1 -1
  120. package/dist/application-state/store.d.ts +11 -0
  121. package/dist/application-state/store.d.ts.map +1 -1
  122. package/dist/application-state/store.js +23 -10
  123. package/dist/application-state/store.js.map +1 -1
  124. package/dist/audit/redact.d.ts +25 -2
  125. package/dist/audit/redact.d.ts.map +1 -1
  126. package/dist/audit/redact.js +85 -21
  127. package/dist/audit/redact.js.map +1 -1
  128. package/dist/client/AgentPanel.d.ts.map +1 -1
  129. package/dist/client/AgentPanel.js +2 -7
  130. package/dist/client/AgentPanel.js.map +1 -1
  131. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  132. package/dist/client/MultiTabAssistantChat.js +1 -1
  133. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  134. package/dist/client/RunStuckBanner.d.ts +11 -1
  135. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  136. package/dist/client/RunStuckBanner.js +20 -7
  137. package/dist/client/RunStuckBanner.js.map +1 -1
  138. package/dist/client/agent-chat.d.ts.map +1 -1
  139. package/dist/client/agent-chat.js +4 -6
  140. package/dist/client/agent-chat.js.map +1 -1
  141. package/dist/client/application-state.d.ts +11 -0
  142. package/dist/client/application-state.d.ts.map +1 -1
  143. package/dist/client/application-state.js +87 -9
  144. package/dist/client/application-state.js.map +1 -1
  145. package/dist/client/dynamic-suggestions.d.ts.map +1 -1
  146. package/dist/client/dynamic-suggestions.js +4 -13
  147. package/dist/client/dynamic-suggestions.js.map +1 -1
  148. package/dist/client/guided-questions.d.ts.map +1 -1
  149. package/dist/client/guided-questions.js +6 -22
  150. package/dist/client/guided-questions.js.map +1 -1
  151. package/dist/collab/routes.d.ts +1 -1
  152. package/dist/collab/struct-routes.d.ts +1 -1
  153. package/dist/db/client.d.ts +57 -10
  154. package/dist/db/client.d.ts.map +1 -1
  155. package/dist/db/client.js +122 -31
  156. package/dist/db/client.js.map +1 -1
  157. package/dist/db/create-get-db.d.ts.map +1 -1
  158. package/dist/db/create-get-db.js +31 -7
  159. package/dist/db/create-get-db.js.map +1 -1
  160. package/dist/db/runtime-diagnostics.d.ts +1 -0
  161. package/dist/db/runtime-diagnostics.d.ts.map +1 -1
  162. package/dist/db/runtime-diagnostics.js +31 -6
  163. package/dist/db/runtime-diagnostics.js.map +1 -1
  164. package/dist/deploy/build.d.ts +5 -20
  165. package/dist/deploy/build.d.ts.map +1 -1
  166. package/dist/deploy/build.js +18 -30
  167. package/dist/deploy/build.js.map +1 -1
  168. package/dist/feature-flags/store.d.ts.map +1 -1
  169. package/dist/feature-flags/store.js +10 -5
  170. package/dist/feature-flags/store.js.map +1 -1
  171. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  172. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  173. package/dist/integrations/a2a-continuations-store.js +12 -0
  174. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  175. package/dist/integrations/config-store.d.ts +1 -0
  176. package/dist/integrations/config-store.d.ts.map +1 -1
  177. package/dist/integrations/config-store.js +11 -0
  178. package/dist/integrations/config-store.js.map +1 -1
  179. package/dist/integrations/google-docs-poller.d.ts.map +1 -1
  180. package/dist/integrations/google-docs-poller.js +19 -2
  181. package/dist/integrations/google-docs-poller.js.map +1 -1
  182. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  183. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  184. package/dist/mcp-client/routes.d.ts.map +1 -1
  185. package/dist/mcp-client/routes.js +30 -2
  186. package/dist/mcp-client/routes.js.map +1 -1
  187. package/dist/notifications/routes.d.ts +3 -3
  188. package/dist/observability/routes.d.ts +1 -1
  189. package/dist/onboarding/plugin.d.ts.map +1 -1
  190. package/dist/onboarding/plugin.js +12 -8
  191. package/dist/onboarding/plugin.js.map +1 -1
  192. package/dist/org/accept-pending.d.ts.map +1 -1
  193. package/dist/org/accept-pending.js +2 -0
  194. package/dist/org/accept-pending.js.map +1 -1
  195. package/dist/org/auto-join-domain.d.ts.map +1 -1
  196. package/dist/org/auto-join-domain.js +2 -0
  197. package/dist/org/auto-join-domain.js.map +1 -1
  198. package/dist/org/context.d.ts.map +1 -1
  199. package/dist/org/context.js +29 -9
  200. package/dist/org/context.js.map +1 -1
  201. package/dist/org/handlers.d.ts.map +1 -1
  202. package/dist/org/handlers.js +5 -0
  203. package/dist/org/handlers.js.map +1 -1
  204. package/dist/org/request-org-cache.d.ts +16 -0
  205. package/dist/org/request-org-cache.d.ts.map +1 -0
  206. package/dist/org/request-org-cache.js +62 -0
  207. package/dist/org/request-org-cache.js.map +1 -0
  208. package/dist/resources/store.d.ts.map +1 -1
  209. package/dist/resources/store.js +11 -9
  210. package/dist/resources/store.js.map +1 -1
  211. package/dist/scripts/runner.d.ts.map +1 -1
  212. package/dist/scripts/runner.js +3 -3
  213. package/dist/scripts/runner.js.map +1 -1
  214. package/dist/secrets/storage.d.ts.map +1 -1
  215. package/dist/secrets/storage.js +82 -5
  216. package/dist/secrets/storage.js.map +1 -1
  217. package/dist/server/action-change.d.ts +20 -0
  218. package/dist/server/action-change.d.ts.map +1 -1
  219. package/dist/server/action-change.js +29 -0
  220. package/dist/server/action-change.js.map +1 -1
  221. package/dist/server/action-routes.d.ts.map +1 -1
  222. package/dist/server/action-routes.js +7 -8
  223. package/dist/server/action-routes.js.map +1 -1
  224. package/dist/server/agent-teams.d.ts.map +1 -1
  225. package/dist/server/agent-teams.js +10 -2
  226. package/dist/server/agent-teams.js.map +1 -1
  227. package/dist/server/better-auth-instance.d.ts.map +1 -1
  228. package/dist/server/better-auth-instance.js +31 -14
  229. package/dist/server/better-auth-instance.js.map +1 -1
  230. package/dist/server/core-routes-plugin.d.ts +1 -0
  231. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  232. package/dist/server/core-routes-plugin.js +18 -4
  233. package/dist/server/core-routes-plugin.js.map +1 -1
  234. package/dist/server/credential-provider.d.ts +16 -0
  235. package/dist/server/credential-provider.d.ts.map +1 -1
  236. package/dist/server/credential-provider.js +49 -13
  237. package/dist/server/credential-provider.js.map +1 -1
  238. package/dist/server/http-response-telemetry.d.ts.map +1 -1
  239. package/dist/server/http-response-telemetry.js +1 -0
  240. package/dist/server/http-response-telemetry.js.map +1 -1
  241. package/dist/server/poll.d.ts +6 -0
  242. package/dist/server/poll.d.ts.map +1 -1
  243. package/dist/server/poll.js +84 -47
  244. package/dist/server/poll.js.map +1 -1
  245. package/dist/server/voice-providers-status.d.ts.map +1 -1
  246. package/dist/server/voice-providers-status.js +12 -3
  247. package/dist/server/voice-providers-status.js.map +1 -1
  248. package/docs/content/durable-background-runs.mdx +15 -6
  249. package/docs/design/durable-agent-runs.md +15 -11
  250. package/package.json +2 -2
  251. package/src/a2a/activity.ts +127 -10
  252. package/src/a2a/index.ts +3 -0
  253. package/src/action.ts +52 -11
  254. package/src/agent/durable-background.ts +38 -28
  255. package/src/agent/harness/background.ts +12 -2
  256. package/src/agent/production-agent.ts +210 -43
  257. package/src/agent/run-store.ts +45 -3
  258. package/src/application-state/handlers.ts +45 -0
  259. package/src/application-state/index.ts +3 -0
  260. package/src/application-state/store.ts +28 -11
  261. package/src/audit/redact.ts +104 -18
  262. package/src/client/AgentPanel.tsx +2 -7
  263. package/src/client/MultiTabAssistantChat.tsx +3 -0
  264. package/src/client/RunStuckBanner.tsx +29 -6
  265. package/src/client/agent-chat.ts +4 -9
  266. package/src/client/application-state.ts +115 -12
  267. package/src/client/dynamic-suggestions.ts +4 -12
  268. package/src/client/guided-questions.tsx +12 -23
  269. package/src/db/client.ts +148 -28
  270. package/src/db/create-get-db.ts +39 -6
  271. package/src/db/runtime-diagnostics.ts +41 -6
  272. package/src/deploy/build.ts +26 -33
  273. package/src/feature-flags/store.ts +10 -5
  274. package/src/integrations/a2a-continuations-store.ts +11 -0
  275. package/src/integrations/config-store.ts +13 -0
  276. package/src/integrations/google-docs-poller.ts +26 -2
  277. package/src/mcp-client/routes.ts +33 -2
  278. package/src/onboarding/plugin.ts +31 -25
  279. package/src/org/accept-pending.ts +2 -0
  280. package/src/org/auto-join-domain.ts +2 -0
  281. package/src/org/context.ts +35 -10
  282. package/src/org/handlers.ts +6 -0
  283. package/src/org/request-org-cache.ts +71 -0
  284. package/src/resources/store.ts +10 -9
  285. package/src/scripts/runner.ts +6 -3
  286. package/src/secrets/storage.ts +97 -5
  287. package/src/server/action-change.ts +33 -0
  288. package/src/server/action-routes.ts +11 -9
  289. package/src/server/agent-teams.ts +10 -2
  290. package/src/server/better-auth-instance.ts +39 -12
  291. package/src/server/core-routes-plugin.ts +19 -2
  292. package/src/server/credential-provider.ts +57 -14
  293. package/src/server/http-response-telemetry.ts +1 -0
  294. package/src/server/poll.ts +95 -57
  295. package/src/server/voice-providers-status.ts +19 -4
@@ -1,4 +1,19 @@
1
1
  export declare const getState: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<Record<string, unknown>>>;
2
+ /** Upper bound on one batched read, so a crafted URL can't fan out unbounded. */
3
+ export declare const MAX_APP_STATE_BATCH_KEYS = 100;
4
+ /**
5
+ * `GET /_agent-native/application-state?keys=a,b,c` — read many keys for the
6
+ * caller's session in one round trip.
7
+ *
8
+ * `values` carries only the keys that have a stored row; every other requested
9
+ * key is listed in `missing`. A key whose stored value is `null` therefore
10
+ * appears in `values` with a `null` value and NOT in `missing`, which is what
11
+ * keeps "no row" distinguishable from "row holding an empty value".
12
+ */
13
+ export declare const getStateMany: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
14
+ values: Record<string, unknown>;
15
+ missing: string[];
16
+ }>>;
2
17
  export declare const putState: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<any>>;
3
18
  export declare const deleteState: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
4
19
  ok: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/application-state/handlers.ts"],"names":[],"mappings":"AAwCA,eAAO,MAAM,QAAQ,wGAKnB,CAAC;AAEH,eAAO,MAAM,QAAQ,oFAOnB,CAAC;AAEH,eAAO,MAAM,WAAW;;GAMtB,CAAC;AAQH,8BAA8B;AAC9B,eAAO,MAAM,iBAAiB,0GAI5B,CAAC;AAEH,iCAAiC;AACjC,eAAO,MAAM,eAAe,wGAK1B,CAAC;AAEH,uCAAuC;AACvC,eAAO,MAAM,eAAe,oFAe1B,CAAC;AAEH,oCAAoC;AACpC,eAAO,MAAM,kBAAkB;;GAM7B,CAAC;AAEH,gCAAgC;AAChC,eAAO,MAAM,sBAAsB;;GAOlC,CAAC"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/application-state/handlers.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,QAAQ,wGAKnB,CAAC;AAEH,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY;;;GA6BvB,CAAC;AAEH,eAAO,MAAM,QAAQ,oFAOnB,CAAC;AAEH,eAAO,MAAM,WAAW;;GAMtB,CAAC;AAQH,8BAA8B;AAC9B,eAAO,MAAM,iBAAiB,0GAI5B,CAAC;AAEH,iCAAiC;AACjC,eAAO,MAAM,eAAe,wGAK1B,CAAC;AAEH,uCAAuC;AACvC,eAAO,MAAM,eAAe,oFAe1B,CAAC;AAEH,oCAAoC;AACpC,eAAO,MAAM,kBAAkB;;GAM7B,CAAC;AAEH,gCAAgC;AAChC,eAAO,MAAM,sBAAsB;;GAOlC,CAAC"}
@@ -1,7 +1,7 @@
1
- import { createError, defineEventHandler, getRouterParam, getHeader, setResponseStatus, } from "h3";
1
+ import { createError, defineEventHandler, getRouterParam, getHeader, getQuery, setResponseStatus, } from "h3";
2
2
  import { getSession } from "../server/auth.js";
3
3
  import { readBody } from "../server/h3-helpers.js";
4
- import { appStateGet, appStatePut, appStateDelete, appStateList, appStateDeleteByPrefix, } from "./store.js";
4
+ import { appStateGet, appStateGetManyEntries, appStatePut, appStateDelete, appStateList, appStateDeleteByPrefix, } from "./store.js";
5
5
  /**
6
6
  * Resolve the session ID for app state scoping. Returns the authenticated
7
7
  * user's email; throws 401 when the request has no session.
@@ -26,6 +26,46 @@ export const getState = defineEventHandler(async (event) => {
26
26
  const value = await appStateGet(sessionId, key);
27
27
  return value ?? null;
28
28
  });
29
+ /** Upper bound on one batched read, so a crafted URL can't fan out unbounded. */
30
+ export const MAX_APP_STATE_BATCH_KEYS = 100;
31
+ /**
32
+ * `GET /_agent-native/application-state?keys=a,b,c` — read many keys for the
33
+ * caller's session in one round trip.
34
+ *
35
+ * `values` carries only the keys that have a stored row; every other requested
36
+ * key is listed in `missing`. A key whose stored value is `null` therefore
37
+ * appears in `values` with a `null` value and NOT in `missing`, which is what
38
+ * keeps "no row" distinguishable from "row holding an empty value".
39
+ */
40
+ export const getStateMany = defineEventHandler(async (event) => {
41
+ const sessionId = await getSessionId(event);
42
+ const raw = getQuery(event).keys;
43
+ const requested = (Array.isArray(raw) ? raw : [raw])
44
+ .flatMap((entry) => String(entry ?? "").split(","))
45
+ .map((key) => safeKey(key.trim()))
46
+ .filter((key) => key.length > 0);
47
+ const keys = [...new Set(requested)];
48
+ if (keys.length === 0) {
49
+ throw createError({
50
+ statusCode: 400,
51
+ statusMessage: "Query parameter `keys` is required",
52
+ });
53
+ }
54
+ if (keys.length > MAX_APP_STATE_BATCH_KEYS) {
55
+ throw createError({
56
+ statusCode: 400,
57
+ statusMessage: `At most ${MAX_APP_STATE_BATCH_KEYS} keys may be read at once`,
58
+ });
59
+ }
60
+ const entries = await appStateGetManyEntries(sessionId, keys);
61
+ const values = {};
62
+ for (const entry of entries)
63
+ values[entry.key] = entry.value;
64
+ return {
65
+ values,
66
+ missing: keys.filter((key) => !(key in values)),
67
+ };
68
+ });
29
69
  export const putState = defineEventHandler(async (event) => {
30
70
  const sessionId = await getSessionId(event);
31
71
  const key = safeKey(String(getRouterParam(event, "key")));
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/application-state/handlers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createError,\n defineEventHandler,\n getRouterParam,\n getHeader,\n setResponseStatus,\n type H3Event,\n} from \"h3\";\n\nimport { getSession } from \"../server/auth.js\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n appStateGet,\n appStatePut,\n appStateDelete,\n appStateList,\n appStateDeleteByPrefix,\n} from \"./store.js\";\n\n/**\n * Resolve the session ID for app state scoping. Returns the authenticated\n * user's email; throws 401 when the request has no session.\n */\nasync function getSessionId(event: H3Event): Promise<string> {\n const session = await getSession(event);\n if (!session?.email) {\n throw createError({\n statusCode: 401,\n statusMessage: \"Unauthenticated\",\n });\n }\n return session.email;\n}\n\nfunction safeKey(key: string): string {\n return key.replace(/[^a-zA-Z0-9_:-]/g, \"\");\n}\n\n// --- Generic state handlers ---\n\nexport const getState = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const key = safeKey(String(getRouterParam(event, \"key\")));\n const value = await appStateGet(sessionId, key);\n return value ?? null;\n});\n\nexport const putState = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const key = safeKey(String(getRouterParam(event, \"key\")));\n const body = await readBody(event);\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStatePut(sessionId, key, body, { requestSource });\n return body;\n});\n\nexport const deleteState = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const key = safeKey(String(getRouterParam(event, \"key\")));\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStateDelete(sessionId, key, { requestSource });\n return { ok: true };\n});\n\n// --- Multi-draft compose handlers ---\n\nfunction composeDraftKey(id: string): string {\n return `compose-${safeKey(id)}`;\n}\n\n/** List all compose drafts */\nexport const listComposeDrafts = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const items = await appStateList(sessionId, \"compose-\");\n return items.map((item) => item.value);\n});\n\n/** Get a single compose draft */\nexport const getComposeDraft = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const id = getRouterParam(event, \"id\") as string;\n const value = await appStateGet(sessionId, composeDraftKey(id));\n return value ?? null;\n});\n\n/** Create or update a compose draft */\nexport const putComposeDraft = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const id = getRouterParam(event, \"id\") as string;\n const body = await readBody(event);\n const { subject, body: bodyText } = body;\n\n if (typeof subject !== \"string\" || typeof bodyText !== \"string\") {\n setResponseStatus(event, 400);\n return { error: \"subject and body are required strings\" };\n }\n\n const state = { ...body, id };\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStatePut(sessionId, composeDraftKey(id), state, { requestSource });\n return state;\n});\n\n/** Delete a single compose draft */\nexport const deleteComposeDraft = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const id = getRouterParam(event, \"id\") as string;\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStateDelete(sessionId, composeDraftKey(id), { requestSource });\n return { ok: true };\n});\n\n/** Delete all compose drafts */\nexport const deleteAllComposeDrafts = defineEventHandler(\n async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStateDeleteByPrefix(sessionId, \"compose-\", { requestSource });\n return { ok: true };\n },\n);\n"]}
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/application-state/handlers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createError,\n defineEventHandler,\n getRouterParam,\n getHeader,\n getQuery,\n setResponseStatus,\n type H3Event,\n} from \"h3\";\n\nimport { getSession } from \"../server/auth.js\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n appStateGet,\n appStateGetManyEntries,\n appStatePut,\n appStateDelete,\n appStateList,\n appStateDeleteByPrefix,\n} from \"./store.js\";\n\n/**\n * Resolve the session ID for app state scoping. Returns the authenticated\n * user's email; throws 401 when the request has no session.\n */\nasync function getSessionId(event: H3Event): Promise<string> {\n const session = await getSession(event);\n if (!session?.email) {\n throw createError({\n statusCode: 401,\n statusMessage: \"Unauthenticated\",\n });\n }\n return session.email;\n}\n\nfunction safeKey(key: string): string {\n return key.replace(/[^a-zA-Z0-9_:-]/g, \"\");\n}\n\n// --- Generic state handlers ---\n\nexport const getState = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const key = safeKey(String(getRouterParam(event, \"key\")));\n const value = await appStateGet(sessionId, key);\n return value ?? null;\n});\n\n/** Upper bound on one batched read, so a crafted URL can't fan out unbounded. */\nexport const MAX_APP_STATE_BATCH_KEYS = 100;\n\n/**\n * `GET /_agent-native/application-state?keys=a,b,c` — read many keys for the\n * caller's session in one round trip.\n *\n * `values` carries only the keys that have a stored row; every other requested\n * key is listed in `missing`. A key whose stored value is `null` therefore\n * appears in `values` with a `null` value and NOT in `missing`, which is what\n * keeps \"no row\" distinguishable from \"row holding an empty value\".\n */\nexport const getStateMany = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const raw = getQuery(event).keys;\n const requested = (Array.isArray(raw) ? raw : [raw])\n .flatMap((entry) => String(entry ?? \"\").split(\",\"))\n .map((key) => safeKey(key.trim()))\n .filter((key) => key.length > 0);\n const keys = [...new Set(requested)];\n\n if (keys.length === 0) {\n throw createError({\n statusCode: 400,\n statusMessage: \"Query parameter `keys` is required\",\n });\n }\n if (keys.length > MAX_APP_STATE_BATCH_KEYS) {\n throw createError({\n statusCode: 400,\n statusMessage: `At most ${MAX_APP_STATE_BATCH_KEYS} keys may be read at once`,\n });\n }\n\n const entries = await appStateGetManyEntries(sessionId, keys);\n const values: Record<string, unknown> = {};\n for (const entry of entries) values[entry.key] = entry.value;\n return {\n values,\n missing: keys.filter((key) => !(key in values)),\n };\n});\n\nexport const putState = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const key = safeKey(String(getRouterParam(event, \"key\")));\n const body = await readBody(event);\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStatePut(sessionId, key, body, { requestSource });\n return body;\n});\n\nexport const deleteState = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const key = safeKey(String(getRouterParam(event, \"key\")));\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStateDelete(sessionId, key, { requestSource });\n return { ok: true };\n});\n\n// --- Multi-draft compose handlers ---\n\nfunction composeDraftKey(id: string): string {\n return `compose-${safeKey(id)}`;\n}\n\n/** List all compose drafts */\nexport const listComposeDrafts = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const items = await appStateList(sessionId, \"compose-\");\n return items.map((item) => item.value);\n});\n\n/** Get a single compose draft */\nexport const getComposeDraft = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const id = getRouterParam(event, \"id\") as string;\n const value = await appStateGet(sessionId, composeDraftKey(id));\n return value ?? null;\n});\n\n/** Create or update a compose draft */\nexport const putComposeDraft = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const id = getRouterParam(event, \"id\") as string;\n const body = await readBody(event);\n const { subject, body: bodyText } = body;\n\n if (typeof subject !== \"string\" || typeof bodyText !== \"string\") {\n setResponseStatus(event, 400);\n return { error: \"subject and body are required strings\" };\n }\n\n const state = { ...body, id };\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStatePut(sessionId, composeDraftKey(id), state, { requestSource });\n return state;\n});\n\n/** Delete a single compose draft */\nexport const deleteComposeDraft = defineEventHandler(async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const id = getRouterParam(event, \"id\") as string;\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStateDelete(sessionId, composeDraftKey(id), { requestSource });\n return { ok: true };\n});\n\n/** Delete all compose drafts */\nexport const deleteAllComposeDrafts = defineEventHandler(\n async (event: H3Event) => {\n const sessionId = await getSessionId(event);\n const requestSource = getHeader(event, \"x-request-source\") || undefined;\n await appStateDeleteByPrefix(sessionId, \"compose-\", { requestSource });\n return { ok: true };\n },\n);\n"]}
@@ -1,5 +1,5 @@
1
- export { appStateGet, appStateGetMany, appStatePut, appStateDelete, appStateCompareAndSet, appStateCompareAndSetMany, appStateList, appStateDeleteByPrefix, type AppStateCompareAndSetOperation, } from "./store.js";
1
+ export { appStateGet, appStateGetMany, appStateGetManyEntries, appStatePut, appStateDelete, appStateCompareAndSet, appStateCompareAndSetMany, appStateList, appStateDeleteByPrefix, type AppStateCompareAndSetOperation, } from "./store.js";
2
2
  export { getAppStateEmitter, emitAppStateChange, emitAppStateDelete, type AppStateEvent, } from "./emitter.js";
3
- export { getState, putState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "./handlers.js";
3
+ export { getState, getStateMany, MAX_APP_STATE_BATCH_KEYS, putState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "./handlers.js";
4
4
  export { readAppState, writeAppState, deleteAppState, compareAndSetAppState, compareAndSetManyAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, } from "./script-helpers.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application-state/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,eAAe,EACf,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,EACZ,sBAAsB,EACtB,KAAK,8BAA8B,GACpC,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application-state/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,eAAe,EACf,sBAAsB,EACtB,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,EACZ,sBAAsB,EACtB,KAAK,8BAA8B,GACpC,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,wBAAwB,EACxB,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC"}
@@ -1,9 +1,9 @@
1
1
  // Store
2
- export { appStateGet, appStateGetMany, appStatePut, appStateDelete, appStateCompareAndSet, appStateCompareAndSetMany, appStateList, appStateDeleteByPrefix, } from "./store.js";
2
+ export { appStateGet, appStateGetMany, appStateGetManyEntries, appStatePut, appStateDelete, appStateCompareAndSet, appStateCompareAndSetMany, appStateList, appStateDeleteByPrefix, } from "./store.js";
3
3
  // Emitter (for SSE wiring)
4
4
  export { getAppStateEmitter, emitAppStateChange, emitAppStateDelete, } from "./emitter.js";
5
5
  // H3 route handlers (for templates)
6
- export { getState, putState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "./handlers.js";
6
+ export { getState, getStateMany, MAX_APP_STATE_BATCH_KEYS, putState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "./handlers.js";
7
7
  // Script helpers
8
8
  export { readAppState, writeAppState, deleteAppState, compareAndSetAppState, compareAndSetManyAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, } from "./script-helpers.js";
9
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/application-state/index.ts"],"names":[],"mappings":"AAAA,QAAQ;AACR,OAAO,EACL,WAAW,EACX,eAAe,EACf,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,EACZ,sBAAsB,GAEvB,MAAM,YAAY,CAAC;AAEpB,2BAA2B;AAC3B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,cAAc,CAAC;AAEtB,oCAAoC;AACpC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC","sourcesContent":["// Store\nexport {\n appStateGet,\n appStateGetMany,\n appStatePut,\n appStateDelete,\n appStateCompareAndSet,\n appStateCompareAndSetMany,\n appStateList,\n appStateDeleteByPrefix,\n type AppStateCompareAndSetOperation,\n} from \"./store.js\";\n\n// Emitter (for SSE wiring)\nexport {\n getAppStateEmitter,\n emitAppStateChange,\n emitAppStateDelete,\n type AppStateEvent,\n} from \"./emitter.js\";\n\n// H3 route handlers (for templates)\nexport {\n getState,\n putState,\n deleteState,\n listComposeDrafts,\n getComposeDraft,\n putComposeDraft,\n deleteComposeDraft,\n deleteAllComposeDrafts,\n} from \"./handlers.js\";\n\n// Script helpers\nexport {\n readAppState,\n writeAppState,\n deleteAppState,\n compareAndSetAppState,\n compareAndSetManyAppState,\n listAppState,\n deleteAppStateByPrefix,\n readAppStateForCurrentTab,\n writeAppStateForCurrentTab,\n appStateKeyForBrowserTab,\n getCurrentRequestBrowserTabId,\n} from \"./script-helpers.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/application-state/index.ts"],"names":[],"mappings":"AAAA,QAAQ;AACR,OAAO,EACL,WAAW,EACX,eAAe,EACf,sBAAsB,EACtB,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,EACZ,sBAAsB,GAEvB,MAAM,YAAY,CAAC;AAEpB,2BAA2B;AAC3B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,cAAc,CAAC;AAEtB,oCAAoC;AACpC,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,wBAAwB,EACxB,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC","sourcesContent":["// Store\nexport {\n appStateGet,\n appStateGetMany,\n appStateGetManyEntries,\n appStatePut,\n appStateDelete,\n appStateCompareAndSet,\n appStateCompareAndSetMany,\n appStateList,\n appStateDeleteByPrefix,\n type AppStateCompareAndSetOperation,\n} from \"./store.js\";\n\n// Emitter (for SSE wiring)\nexport {\n getAppStateEmitter,\n emitAppStateChange,\n emitAppStateDelete,\n type AppStateEvent,\n} from \"./emitter.js\";\n\n// H3 route handlers (for templates)\nexport {\n getState,\n getStateMany,\n MAX_APP_STATE_BATCH_KEYS,\n putState,\n deleteState,\n listComposeDrafts,\n getComposeDraft,\n putComposeDraft,\n deleteComposeDraft,\n deleteAllComposeDrafts,\n} from \"./handlers.js\";\n\n// Script helpers\nexport {\n readAppState,\n writeAppState,\n deleteAppState,\n compareAndSetAppState,\n compareAndSetManyAppState,\n listAppState,\n deleteAppStateByPrefix,\n readAppStateForCurrentTab,\n writeAppStateForCurrentTab,\n appStateKeyForBrowserTab,\n getCurrentRequestBrowserTabId,\n} from \"./script-helpers.js\";\n"]}
@@ -1,5 +1,16 @@
1
1
  import type { StoreWriteOptions } from "../settings/store.js";
2
2
  export declare function appStateGet(sessionId: string, key: string): Promise<Record<string, unknown> | null>;
3
+ /**
4
+ * Read several application-state keys for one session in a single SQL query,
5
+ * returning ONLY the rows that exist. A key absent from the result has no row;
6
+ * a key present with a `null` value has a row that stores `null`. Callers that
7
+ * must not conflate the two (the batched HTTP read) depend on that difference,
8
+ * so this deliberately does not pad missing keys.
9
+ */
10
+ export declare function appStateGetManyEntries(sessionId: string, keys: readonly string[]): Promise<Array<{
11
+ key: string;
12
+ value: Record<string, unknown> | null;
13
+ }>>;
3
14
  /**
4
15
  * Read several application-state keys for one session in a single SQL query.
5
16
  * Missing keys are returned as `null` so callers can preserve the requested
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/application-state/store.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAyF9D,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CASzC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAiBzD;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EAC7C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACzC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AA0GD,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,SAAS,8BAA8B,EAAE,EACrD,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CA+FlB;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,CAWjE;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAsBjB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/application-state/store.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAyF9D,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CASzC;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC,CAcxE;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAQzD;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EAC7C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACzC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AA0GD,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,SAAS,8BAA8B,EAAE,EACrD,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CA+FlB;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,CAWjE;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAsBjB"}
@@ -90,17 +90,16 @@ export async function appStateGet(sessionId, key) {
90
90
  return JSON.parse(rows[0].value);
91
91
  }
92
92
  /**
93
- * Read several application-state keys for one session in a single SQL query.
94
- * Missing keys are returned as `null` so callers can preserve the requested
95
- * shape without issuing one fallback query per key.
93
+ * Read several application-state keys for one session in a single SQL query,
94
+ * returning ONLY the rows that exist. A key absent from the result has no row;
95
+ * a key present with a `null` value has a row that stores `null`. Callers that
96
+ * must not conflate the two (the batched HTTP read) depend on that difference,
97
+ * so this deliberately does not pad missing keys.
96
98
  */
97
- export async function appStateGetMany(sessionId, keys) {
99
+ export async function appStateGetManyEntries(sessionId, keys) {
98
100
  const uniqueKeys = [...new Set(keys)];
99
- const values = {};
100
- for (const key of uniqueKeys)
101
- values[key] = null;
102
101
  if (uniqueKeys.length === 0)
103
- return values;
102
+ return [];
104
103
  await ensureTable();
105
104
  const client = getDbExec();
106
105
  const placeholders = uniqueKeys.map(() => "?").join(", ");
@@ -108,8 +107,22 @@ export async function appStateGetMany(sessionId, keys) {
108
107
  sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key IN (${placeholders})`,
109
108
  args: [sessionId, ...uniqueKeys],
110
109
  });
111
- for (const row of rows) {
112
- values[row.key] = JSON.parse(row.value);
110
+ return rows.map((row) => ({
111
+ key: row.key,
112
+ value: JSON.parse(row.value),
113
+ }));
114
+ }
115
+ /**
116
+ * Read several application-state keys for one session in a single SQL query.
117
+ * Missing keys are returned as `null` so callers can preserve the requested
118
+ * shape without issuing one fallback query per key.
119
+ */
120
+ export async function appStateGetMany(sessionId, keys) {
121
+ const values = {};
122
+ for (const key of new Set(keys))
123
+ values[key] = null;
124
+ for (const entry of await appStateGetManyEntries(sessionId, keys)) {
125
+ values[entry.key] = entry.value;
113
126
  }
114
127
  return values;
115
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/application-state/store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getDbExec,\n getDialect,\n isLocalDatabase,\n isPostgres,\n intType,\n type DbExec,\n} from \"../db/client.js\";\nimport { ensureIndexExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport { widenIntColumnsToBigInt } from \"../db/widen-columns.js\";\nimport type { StoreWriteOptions } from \"../settings/store.js\";\nimport { emitAppStateChange, emitAppStateDelete } from \"./emitter.js\";\n\nlet _initPromise: Promise<void> | undefined;\nconst MAX_HOSTED_APP_STATE_VALUE_BYTES = 1024 * 1024;\n\n// Escapes LIKE wildcards (`%`, `_`) and the escape char itself so a caller's\n// literal prefix is matched verbatim. Used with `ESCAPE '!'` in prefix queries\n// below; without this, a prefix such as `user_settings` would treat `_` as a\n// single-char wildcard and over-match (e.g. delete `userXsettings`).\nfunction escapeLike(s: string): string {\n return s.replace(/[!%_]/g, (match) => `!${match}`);\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createSql = `\n CREATE TABLE IF NOT EXISTS application_state (\n session_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n updated_at ${intType()} NOT NULL,\n PRIMARY KEY (session_id, key)\n )\n `;\n\n if (isPostgres()) {\n // Hot path: the `application_state` table and its poll indexes are\n // virtually always already present in production. Issuing `CREATE\n // TABLE`/ `CREATE INDEX` still takes a lock that, in a fresh\n // background-worker process behind a concurrent connection on the\n // shared Neon DB, can block ~indefinitely (ACCESS EXCLUSIVE for CREATE\n // TABLE; a write-blocking SHARE lock for CREATE INDEX). The ensure*\n // wrappers probe `information_schema`/`pg_indexes` first (plain reads,\n // no lock) and run DDL ONLY for what is actually missing, bounded by a\n // transaction-scoped `lock_timeout`. If a swallowed lock-timeout leaves\n // the schema still missing they RE-PROBE and THROW rather than letting\n // init memoize success against absent schema.\n await ensureTableExists(\"application_state\", createSql);\n // Older deployments created `updated_at` as 32-bit `INTEGER`; on Postgres\n // the `Date.now()` written by appStatePut() on every turn overflows int4.\n // Widen it in place (no-op once done / on fresh BIGINT databases).\n await widenIntColumnsToBigInt(\"application_state\", [\"updated_at\"]);\n // Indexes for the two hot poll paths. Probe pg_indexes first (no lock)\n // and skip the SHARE-locking CREATE INDEX when already present.\n await ensureIndexExists(\n \"app_state_updated_at_idx\",\n `CREATE INDEX IF NOT EXISTS app_state_updated_at_idx ON application_state (updated_at)`,\n );\n await ensureIndexExists(\n \"app_state_key_updated_idx\",\n `CREATE INDEX IF NOT EXISTS app_state_key_updated_idx ON application_state (key, updated_at)`,\n );\n return;\n }\n\n // SQLite (local dev): no lock problem — keep the original behaviour.\n await client.execute(createSql);\n // Older deployments created `updated_at` as 32-bit `INTEGER`; on Postgres\n // the `Date.now()` written by appStatePut() on every turn overflows int4.\n // Widen it in place (no-op once done / on fresh BIGINT databases).\n await widenIntColumnsToBigInt(\"application_state\", [\"updated_at\"]);\n // Indexes for the two hot poll paths:\n // - `SELECT … WHERE updated_at > ?` (watermark scan, every poll cycle)\n // - `SELECT … WHERE key = ? … ORDER BY updated_at ASC` (marker lookups)\n // Both are dialect-agnostic (no DESC/partial/PG-only syntax) so they\n // apply identically on SQLite and Postgres. IF NOT EXISTS makes them\n // idempotent across restarts on existing databases.\n for (const ddl of [\n `CREATE INDEX IF NOT EXISTS app_state_updated_at_idx ON application_state (updated_at)`,\n `CREATE INDEX IF NOT EXISTS app_state_key_updated_idx ON application_state (key, updated_at)`,\n ]) {\n try {\n await client.execute(ddl);\n } catch {\n // Index already exists or the dialect rejected a duplicate.\n }\n }\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nexport async function appStateGet(\n sessionId: string,\n key: string,\n): Promise<Record<string, unknown> | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT value FROM application_state WHERE session_id = ? AND key = ?`,\n args: [sessionId, key],\n });\n if (rows.length === 0) return null;\n return JSON.parse(rows[0].value as string);\n}\n\n/**\n * Read several application-state keys for one session in a single SQL query.\n * Missing keys are returned as `null` so callers can preserve the requested\n * shape without issuing one fallback query per key.\n */\nexport async function appStateGetMany(\n sessionId: string,\n keys: readonly string[],\n): Promise<Record<string, Record<string, unknown> | null>> {\n const uniqueKeys = [...new Set(keys)];\n const values: Record<string, Record<string, unknown> | null> = {};\n for (const key of uniqueKeys) values[key] = null;\n if (uniqueKeys.length === 0) return values;\n\n await ensureTable();\n const client = getDbExec();\n const placeholders = uniqueKeys.map(() => \"?\").join(\", \");\n const { rows } = await client.execute({\n sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key IN (${placeholders})`,\n args: [sessionId, ...uniqueKeys],\n });\n for (const row of rows) {\n values[row.key as string] = JSON.parse(row.value as string);\n }\n return values;\n}\n\nexport async function appStatePut(\n sessionId: string,\n key: string,\n value: Record<string, unknown>,\n options?: StoreWriteOptions,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const serialized = JSON.stringify(value);\n if (\n !isLocalDatabase() &&\n Buffer.byteLength(serialized, \"utf8\") > MAX_HOSTED_APP_STATE_VALUE_BYTES\n ) {\n throw new Error(\n `application_state value \"${key}\" is too large for hosted SQL storage. Store large files, base64, or blobs in file storage and write only a URL or handle.`,\n );\n }\n await client.execute({\n sql: isPostgres()\n ? `INSERT INTO application_state (session_id, key, value, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT (session_id, key) DO UPDATE SET value=EXCLUDED.value, updated_at=EXCLUDED.updated_at`\n : `INSERT OR REPLACE INTO application_state (session_id, key, value, updated_at) VALUES (?, ?, ?, ?)`,\n args: [sessionId, key, serialized, Date.now()],\n });\n emitAppStateChange(key, options?.requestSource, sessionId);\n}\n\nexport async function appStateDelete(\n sessionId: string,\n key: string,\n options?: StoreWriteOptions,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `DELETE FROM application_state WHERE session_id = ? AND key = ?`,\n args: [sessionId, key],\n });\n const deleted = result.rowsAffected > 0;\n if (deleted) emitAppStateDelete(key, options?.requestSource, sessionId);\n return deleted;\n}\n\nexport async function appStateCompareAndSet(\n sessionId: string,\n key: string,\n expectedValue: Record<string, unknown> | null,\n nextValue: Record<string, unknown> | null,\n options?: StoreWriteOptions,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const changed = await executeAppStateCompareAndSet(\n client,\n sessionId,\n key,\n expectedValue,\n nextValue,\n );\n if (changed) {\n if (nextValue === null) {\n emitAppStateDelete(key, options?.requestSource, sessionId);\n } else {\n emitAppStateChange(key, options?.requestSource, sessionId);\n }\n }\n return changed;\n}\n\nexport interface AppStateCompareAndSetOperation {\n key: string;\n expectedValue: Record<string, unknown> | null;\n nextValue: Record<string, unknown> | null;\n}\n\nconst APP_STATE_CAS_MISMATCH = Symbol(\"app-state-cas-mismatch\");\n\nasync function executeAppStateCompareAndSet(\n client: DbExec,\n sessionId: string,\n key: string,\n expectedValue: Record<string, unknown> | null,\n nextValue: Record<string, unknown> | null,\n): Promise<boolean> {\n const statement = buildAppStateCompareAndSetStatement(\n sessionId,\n key,\n expectedValue,\n nextValue,\n );\n const result = await client.execute(statement);\n return result.rowsAffected > 0;\n}\n\nfunction buildAppStateCompareAndSetStatement(\n sessionId: string,\n key: string,\n expectedValue: Record<string, unknown> | null,\n nextValue: Record<string, unknown> | null,\n): { sql: string; args: unknown[] } {\n if (expectedValue === null) {\n if (nextValue === null) {\n throw new Error(\n \"Application state CAS cannot replace absence with absence.\",\n );\n }\n const next = serializeAppStateValue(key, nextValue);\n return {\n sql: isPostgres()\n ? `INSERT INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?) ON CONFLICT (session_id, key) DO NOTHING`\n : `INSERT OR IGNORE INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?)`,\n args: [sessionId, key, next, Date.now(), sessionId, key],\n };\n }\n\n const expected = JSON.stringify(expectedValue);\n if (nextValue === null) {\n return {\n sql: `DELETE FROM application_state WHERE session_id = ? AND key = ? AND value = ?`,\n args: [sessionId, key, expected],\n };\n }\n\n const next = serializeAppStateValue(key, nextValue);\n return {\n sql: `UPDATE application_state SET value = ?, updated_at = ? WHERE session_id = ? AND key = ? AND value = ?`,\n args: [next, Date.now(), sessionId, key, expected],\n };\n}\n\nfunction buildD1CompareAndSetGuard(\n sessionId: string,\n guardKey: string,\n operation: AppStateCompareAndSetOperation,\n): { sql: string; args: unknown[] } {\n const expected = operation.expectedValue;\n const condition =\n expected === null\n ? \"NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?)\"\n : \"EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ? AND value = ?)\";\n return {\n sql: `INSERT INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, '{}', ? WHERE NOT (${condition})`,\n args:\n expected === null\n ? [sessionId, guardKey, Date.now(), sessionId, operation.key]\n : [\n sessionId,\n guardKey,\n Date.now(),\n sessionId,\n operation.key,\n JSON.stringify(expected),\n ],\n };\n}\n\nfunction isD1CompareAndSetMismatch(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error);\n return /unique constraint failed:\\s*application_state\\.session_id,\\s*application_state\\.key/i.test(\n message,\n );\n}\n\nfunction serializeAppStateValue(\n key: string,\n value: Record<string, unknown>,\n): string {\n const serialized = JSON.stringify(value);\n if (\n !isLocalDatabase() &&\n Buffer.byteLength(serialized, \"utf8\") > MAX_HOSTED_APP_STATE_VALUE_BYTES\n ) {\n throw new Error(\n `application_state value \"${key}\" is too large for hosted SQL storage. Store large files, base64, or blobs in file storage and write only a URL or handle.`,\n );\n }\n return serialized;\n}\n\nexport async function appStateCompareAndSetMany(\n sessionId: string,\n operations: readonly AppStateCompareAndSetOperation[],\n options?: StoreWriteOptions,\n): Promise<boolean> {\n if (operations.length === 0) return true;\n const keys = new Set(operations.map(({ key }) => key));\n if (keys.size !== operations.length) {\n throw new Error(\"Application state multi-key CAS requires unique keys.\");\n }\n for (const { key, nextValue } of operations) {\n if (nextValue) serializeAppStateValue(key, nextValue);\n }\n const orderedOperations = [...operations].sort((a, b) =>\n a.key.localeCompare(b.key),\n );\n\n await ensureTable();\n const client = getDbExec();\n if (getDialect() === \"d1\") {\n if (!client.atomicBatch) {\n throw new Error(\n \"D1 application-state CAS requires atomic batch support.\",\n );\n }\n const guardKey = `__agent_native_cas_guard__:${Date.now()}:${Math.random().toString(36).slice(2)}`;\n const guardInsert = {\n sql: `INSERT INTO application_state (session_id, key, value, updated_at) VALUES (?, ?, '{}', ?)`,\n args: [sessionId, guardKey, Date.now()],\n };\n const guards = orderedOperations.map((operation) =>\n buildD1CompareAndSetGuard(sessionId, guardKey, operation),\n );\n const mutations = orderedOperations.map((operation) =>\n buildAppStateCompareAndSetStatement(\n sessionId,\n operation.key,\n operation.expectedValue,\n operation.nextValue,\n ),\n );\n let results;\n try {\n results = await client.atomicBatch([\n guardInsert,\n ...guards,\n ...mutations,\n {\n sql: `DELETE FROM application_state WHERE session_id = ? AND key = ?`,\n args: [sessionId, guardKey],\n },\n ]);\n } catch (error) {\n if (isD1CompareAndSetMismatch(error)) return false;\n throw error;\n }\n const mutationResults = results.slice(\n 1 + guards.length,\n 1 + guards.length + mutations.length,\n );\n if (\n mutationResults.length !== mutations.length ||\n mutationResults.some((result) => result.rowsAffected !== 1)\n ) {\n throw new Error(\n \"D1 application-state CAS completed without applying every mutation.\",\n );\n }\n } else {\n if (!client.transaction) {\n throw new Error(\"Application state multi-key CAS requires transactions.\");\n }\n try {\n await client.transaction(async (tx) => {\n for (const operation of orderedOperations) {\n const changed = await executeAppStateCompareAndSet(\n tx,\n sessionId,\n operation.key,\n operation.expectedValue,\n operation.nextValue,\n );\n if (!changed) throw APP_STATE_CAS_MISMATCH;\n }\n });\n } catch (error) {\n if (error === APP_STATE_CAS_MISMATCH) return false;\n throw error;\n }\n }\n\n for (const { key, nextValue } of operations) {\n if (nextValue === null) {\n emitAppStateDelete(key, options?.requestSource, sessionId);\n } else {\n emitAppStateChange(key, options?.requestSource, sessionId);\n }\n }\n return true;\n}\n\nexport async function appStateList(\n sessionId: string,\n keyPrefix: string,\n): Promise<Array<{ key: string; value: Record<string, unknown> }>> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!'`,\n args: [sessionId, escapeLike(keyPrefix) + \"%\"],\n });\n return rows.map((row) => ({\n key: row.key as string,\n value: JSON.parse(row.value as string),\n }));\n}\n\nexport async function appStateDeleteByPrefix(\n sessionId: string,\n keyPrefix: string,\n options?: StoreWriteOptions,\n): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n\n // Get keys first so we can emit events\n const { rows } = await client.execute({\n sql: `SELECT key FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!'`,\n args: [sessionId, escapeLike(keyPrefix) + \"%\"],\n });\n\n if (rows.length === 0) return 0;\n\n const result = await client.execute({\n sql: `DELETE FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!'`,\n args: [sessionId, escapeLike(keyPrefix) + \"%\"],\n });\n\n for (const row of rows) {\n emitAppStateDelete(row.key as string, options?.requestSource, sessionId);\n }\n\n return result.rowsAffected;\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/application-state/store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getDbExec,\n getDialect,\n isLocalDatabase,\n isPostgres,\n intType,\n type DbExec,\n} from \"../db/client.js\";\nimport { ensureIndexExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport { widenIntColumnsToBigInt } from \"../db/widen-columns.js\";\nimport type { StoreWriteOptions } from \"../settings/store.js\";\nimport { emitAppStateChange, emitAppStateDelete } from \"./emitter.js\";\n\nlet _initPromise: Promise<void> | undefined;\nconst MAX_HOSTED_APP_STATE_VALUE_BYTES = 1024 * 1024;\n\n// Escapes LIKE wildcards (`%`, `_`) and the escape char itself so a caller's\n// literal prefix is matched verbatim. Used with `ESCAPE '!'` in prefix queries\n// below; without this, a prefix such as `user_settings` would treat `_` as a\n// single-char wildcard and over-match (e.g. delete `userXsettings`).\nfunction escapeLike(s: string): string {\n return s.replace(/[!%_]/g, (match) => `!${match}`);\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createSql = `\n CREATE TABLE IF NOT EXISTS application_state (\n session_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n updated_at ${intType()} NOT NULL,\n PRIMARY KEY (session_id, key)\n )\n `;\n\n if (isPostgres()) {\n // Hot path: the `application_state` table and its poll indexes are\n // virtually always already present in production. Issuing `CREATE\n // TABLE`/ `CREATE INDEX` still takes a lock that, in a fresh\n // background-worker process behind a concurrent connection on the\n // shared Neon DB, can block ~indefinitely (ACCESS EXCLUSIVE for CREATE\n // TABLE; a write-blocking SHARE lock for CREATE INDEX). The ensure*\n // wrappers probe `information_schema`/`pg_indexes` first (plain reads,\n // no lock) and run DDL ONLY for what is actually missing, bounded by a\n // transaction-scoped `lock_timeout`. If a swallowed lock-timeout leaves\n // the schema still missing they RE-PROBE and THROW rather than letting\n // init memoize success against absent schema.\n await ensureTableExists(\"application_state\", createSql);\n // Older deployments created `updated_at` as 32-bit `INTEGER`; on Postgres\n // the `Date.now()` written by appStatePut() on every turn overflows int4.\n // Widen it in place (no-op once done / on fresh BIGINT databases).\n await widenIntColumnsToBigInt(\"application_state\", [\"updated_at\"]);\n // Indexes for the two hot poll paths. Probe pg_indexes first (no lock)\n // and skip the SHARE-locking CREATE INDEX when already present.\n await ensureIndexExists(\n \"app_state_updated_at_idx\",\n `CREATE INDEX IF NOT EXISTS app_state_updated_at_idx ON application_state (updated_at)`,\n );\n await ensureIndexExists(\n \"app_state_key_updated_idx\",\n `CREATE INDEX IF NOT EXISTS app_state_key_updated_idx ON application_state (key, updated_at)`,\n );\n return;\n }\n\n // SQLite (local dev): no lock problem — keep the original behaviour.\n await client.execute(createSql);\n // Older deployments created `updated_at` as 32-bit `INTEGER`; on Postgres\n // the `Date.now()` written by appStatePut() on every turn overflows int4.\n // Widen it in place (no-op once done / on fresh BIGINT databases).\n await widenIntColumnsToBigInt(\"application_state\", [\"updated_at\"]);\n // Indexes for the two hot poll paths:\n // - `SELECT … WHERE updated_at > ?` (watermark scan, every poll cycle)\n // - `SELECT … WHERE key = ? … ORDER BY updated_at ASC` (marker lookups)\n // Both are dialect-agnostic (no DESC/partial/PG-only syntax) so they\n // apply identically on SQLite and Postgres. IF NOT EXISTS makes them\n // idempotent across restarts on existing databases.\n for (const ddl of [\n `CREATE INDEX IF NOT EXISTS app_state_updated_at_idx ON application_state (updated_at)`,\n `CREATE INDEX IF NOT EXISTS app_state_key_updated_idx ON application_state (key, updated_at)`,\n ]) {\n try {\n await client.execute(ddl);\n } catch {\n // Index already exists or the dialect rejected a duplicate.\n }\n }\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nexport async function appStateGet(\n sessionId: string,\n key: string,\n): Promise<Record<string, unknown> | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT value FROM application_state WHERE session_id = ? AND key = ?`,\n args: [sessionId, key],\n });\n if (rows.length === 0) return null;\n return JSON.parse(rows[0].value as string);\n}\n\n/**\n * Read several application-state keys for one session in a single SQL query,\n * returning ONLY the rows that exist. A key absent from the result has no row;\n * a key present with a `null` value has a row that stores `null`. Callers that\n * must not conflate the two (the batched HTTP read) depend on that difference,\n * so this deliberately does not pad missing keys.\n */\nexport async function appStateGetManyEntries(\n sessionId: string,\n keys: readonly string[],\n): Promise<Array<{ key: string; value: Record<string, unknown> | null }>> {\n const uniqueKeys = [...new Set(keys)];\n if (uniqueKeys.length === 0) return [];\n await ensureTable();\n const client = getDbExec();\n const placeholders = uniqueKeys.map(() => \"?\").join(\", \");\n const { rows } = await client.execute({\n sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key IN (${placeholders})`,\n args: [sessionId, ...uniqueKeys],\n });\n return rows.map((row) => ({\n key: row.key as string,\n value: JSON.parse(row.value as string),\n }));\n}\n\n/**\n * Read several application-state keys for one session in a single SQL query.\n * Missing keys are returned as `null` so callers can preserve the requested\n * shape without issuing one fallback query per key.\n */\nexport async function appStateGetMany(\n sessionId: string,\n keys: readonly string[],\n): Promise<Record<string, Record<string, unknown> | null>> {\n const values: Record<string, Record<string, unknown> | null> = {};\n for (const key of new Set(keys)) values[key] = null;\n\n for (const entry of await appStateGetManyEntries(sessionId, keys)) {\n values[entry.key] = entry.value;\n }\n return values;\n}\n\nexport async function appStatePut(\n sessionId: string,\n key: string,\n value: Record<string, unknown>,\n options?: StoreWriteOptions,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const serialized = JSON.stringify(value);\n if (\n !isLocalDatabase() &&\n Buffer.byteLength(serialized, \"utf8\") > MAX_HOSTED_APP_STATE_VALUE_BYTES\n ) {\n throw new Error(\n `application_state value \"${key}\" is too large for hosted SQL storage. Store large files, base64, or blobs in file storage and write only a URL or handle.`,\n );\n }\n await client.execute({\n sql: isPostgres()\n ? `INSERT INTO application_state (session_id, key, value, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT (session_id, key) DO UPDATE SET value=EXCLUDED.value, updated_at=EXCLUDED.updated_at`\n : `INSERT OR REPLACE INTO application_state (session_id, key, value, updated_at) VALUES (?, ?, ?, ?)`,\n args: [sessionId, key, serialized, Date.now()],\n });\n emitAppStateChange(key, options?.requestSource, sessionId);\n}\n\nexport async function appStateDelete(\n sessionId: string,\n key: string,\n options?: StoreWriteOptions,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `DELETE FROM application_state WHERE session_id = ? AND key = ?`,\n args: [sessionId, key],\n });\n const deleted = result.rowsAffected > 0;\n if (deleted) emitAppStateDelete(key, options?.requestSource, sessionId);\n return deleted;\n}\n\nexport async function appStateCompareAndSet(\n sessionId: string,\n key: string,\n expectedValue: Record<string, unknown> | null,\n nextValue: Record<string, unknown> | null,\n options?: StoreWriteOptions,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const changed = await executeAppStateCompareAndSet(\n client,\n sessionId,\n key,\n expectedValue,\n nextValue,\n );\n if (changed) {\n if (nextValue === null) {\n emitAppStateDelete(key, options?.requestSource, sessionId);\n } else {\n emitAppStateChange(key, options?.requestSource, sessionId);\n }\n }\n return changed;\n}\n\nexport interface AppStateCompareAndSetOperation {\n key: string;\n expectedValue: Record<string, unknown> | null;\n nextValue: Record<string, unknown> | null;\n}\n\nconst APP_STATE_CAS_MISMATCH = Symbol(\"app-state-cas-mismatch\");\n\nasync function executeAppStateCompareAndSet(\n client: DbExec,\n sessionId: string,\n key: string,\n expectedValue: Record<string, unknown> | null,\n nextValue: Record<string, unknown> | null,\n): Promise<boolean> {\n const statement = buildAppStateCompareAndSetStatement(\n sessionId,\n key,\n expectedValue,\n nextValue,\n );\n const result = await client.execute(statement);\n return result.rowsAffected > 0;\n}\n\nfunction buildAppStateCompareAndSetStatement(\n sessionId: string,\n key: string,\n expectedValue: Record<string, unknown> | null,\n nextValue: Record<string, unknown> | null,\n): { sql: string; args: unknown[] } {\n if (expectedValue === null) {\n if (nextValue === null) {\n throw new Error(\n \"Application state CAS cannot replace absence with absence.\",\n );\n }\n const next = serializeAppStateValue(key, nextValue);\n return {\n sql: isPostgres()\n ? `INSERT INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?) ON CONFLICT (session_id, key) DO NOTHING`\n : `INSERT OR IGNORE INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?)`,\n args: [sessionId, key, next, Date.now(), sessionId, key],\n };\n }\n\n const expected = JSON.stringify(expectedValue);\n if (nextValue === null) {\n return {\n sql: `DELETE FROM application_state WHERE session_id = ? AND key = ? AND value = ?`,\n args: [sessionId, key, expected],\n };\n }\n\n const next = serializeAppStateValue(key, nextValue);\n return {\n sql: `UPDATE application_state SET value = ?, updated_at = ? WHERE session_id = ? AND key = ? AND value = ?`,\n args: [next, Date.now(), sessionId, key, expected],\n };\n}\n\nfunction buildD1CompareAndSetGuard(\n sessionId: string,\n guardKey: string,\n operation: AppStateCompareAndSetOperation,\n): { sql: string; args: unknown[] } {\n const expected = operation.expectedValue;\n const condition =\n expected === null\n ? \"NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?)\"\n : \"EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ? AND value = ?)\";\n return {\n sql: `INSERT INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, '{}', ? WHERE NOT (${condition})`,\n args:\n expected === null\n ? [sessionId, guardKey, Date.now(), sessionId, operation.key]\n : [\n sessionId,\n guardKey,\n Date.now(),\n sessionId,\n operation.key,\n JSON.stringify(expected),\n ],\n };\n}\n\nfunction isD1CompareAndSetMismatch(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error);\n return /unique constraint failed:\\s*application_state\\.session_id,\\s*application_state\\.key/i.test(\n message,\n );\n}\n\nfunction serializeAppStateValue(\n key: string,\n value: Record<string, unknown>,\n): string {\n const serialized = JSON.stringify(value);\n if (\n !isLocalDatabase() &&\n Buffer.byteLength(serialized, \"utf8\") > MAX_HOSTED_APP_STATE_VALUE_BYTES\n ) {\n throw new Error(\n `application_state value \"${key}\" is too large for hosted SQL storage. Store large files, base64, or blobs in file storage and write only a URL or handle.`,\n );\n }\n return serialized;\n}\n\nexport async function appStateCompareAndSetMany(\n sessionId: string,\n operations: readonly AppStateCompareAndSetOperation[],\n options?: StoreWriteOptions,\n): Promise<boolean> {\n if (operations.length === 0) return true;\n const keys = new Set(operations.map(({ key }) => key));\n if (keys.size !== operations.length) {\n throw new Error(\"Application state multi-key CAS requires unique keys.\");\n }\n for (const { key, nextValue } of operations) {\n if (nextValue) serializeAppStateValue(key, nextValue);\n }\n const orderedOperations = [...operations].sort((a, b) =>\n a.key.localeCompare(b.key),\n );\n\n await ensureTable();\n const client = getDbExec();\n if (getDialect() === \"d1\") {\n if (!client.atomicBatch) {\n throw new Error(\n \"D1 application-state CAS requires atomic batch support.\",\n );\n }\n const guardKey = `__agent_native_cas_guard__:${Date.now()}:${Math.random().toString(36).slice(2)}`;\n const guardInsert = {\n sql: `INSERT INTO application_state (session_id, key, value, updated_at) VALUES (?, ?, '{}', ?)`,\n args: [sessionId, guardKey, Date.now()],\n };\n const guards = orderedOperations.map((operation) =>\n buildD1CompareAndSetGuard(sessionId, guardKey, operation),\n );\n const mutations = orderedOperations.map((operation) =>\n buildAppStateCompareAndSetStatement(\n sessionId,\n operation.key,\n operation.expectedValue,\n operation.nextValue,\n ),\n );\n let results;\n try {\n results = await client.atomicBatch([\n guardInsert,\n ...guards,\n ...mutations,\n {\n sql: `DELETE FROM application_state WHERE session_id = ? AND key = ?`,\n args: [sessionId, guardKey],\n },\n ]);\n } catch (error) {\n if (isD1CompareAndSetMismatch(error)) return false;\n throw error;\n }\n const mutationResults = results.slice(\n 1 + guards.length,\n 1 + guards.length + mutations.length,\n );\n if (\n mutationResults.length !== mutations.length ||\n mutationResults.some((result) => result.rowsAffected !== 1)\n ) {\n throw new Error(\n \"D1 application-state CAS completed without applying every mutation.\",\n );\n }\n } else {\n if (!client.transaction) {\n throw new Error(\"Application state multi-key CAS requires transactions.\");\n }\n try {\n await client.transaction(async (tx) => {\n for (const operation of orderedOperations) {\n const changed = await executeAppStateCompareAndSet(\n tx,\n sessionId,\n operation.key,\n operation.expectedValue,\n operation.nextValue,\n );\n if (!changed) throw APP_STATE_CAS_MISMATCH;\n }\n });\n } catch (error) {\n if (error === APP_STATE_CAS_MISMATCH) return false;\n throw error;\n }\n }\n\n for (const { key, nextValue } of operations) {\n if (nextValue === null) {\n emitAppStateDelete(key, options?.requestSource, sessionId);\n } else {\n emitAppStateChange(key, options?.requestSource, sessionId);\n }\n }\n return true;\n}\n\nexport async function appStateList(\n sessionId: string,\n keyPrefix: string,\n): Promise<Array<{ key: string; value: Record<string, unknown> }>> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!'`,\n args: [sessionId, escapeLike(keyPrefix) + \"%\"],\n });\n return rows.map((row) => ({\n key: row.key as string,\n value: JSON.parse(row.value as string),\n }));\n}\n\nexport async function appStateDeleteByPrefix(\n sessionId: string,\n keyPrefix: string,\n options?: StoreWriteOptions,\n): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n\n // Get keys first so we can emit events\n const { rows } = await client.execute({\n sql: `SELECT key FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!'`,\n args: [sessionId, escapeLike(keyPrefix) + \"%\"],\n });\n\n if (rows.length === 0) return 0;\n\n const result = await client.execute({\n sql: `DELETE FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!'`,\n args: [sessionId, escapeLike(keyPrefix) + \"%\"],\n });\n\n for (const row of rows) {\n emitAppStateDelete(row.key as string, options?.requestSource, sessionId);\n }\n\n return result.rowsAffected;\n}\n"]}
@@ -12,16 +12,39 @@
12
12
  */
13
13
  /** Heuristic: does a bare string value look like a secret? */
14
14
  declare function looksSecret(value: string): boolean;
15
- declare function redact(value: unknown, depth: number): unknown;
15
+ declare function redactEmbeddedSecrets(value: string): string;
16
+ declare function redact(value: unknown, depth: number, maxString: number): unknown;
17
+ export interface RedactLimits {
18
+ /**
19
+ * Hard cap on the serialized output, INCLUDING the truncation envelope.
20
+ * Surfaces with their own wire budget (the A2A activity snapshot) pass a
21
+ * tighter cap than the audit-log default.
22
+ */
23
+ maxJson?: number;
24
+ /** Cap on any single string value before it gets a truncation marker. */
25
+ maxString?: number;
26
+ }
16
27
  /**
17
28
  * Redact and serialize call arguments to a capped JSON string, or `null` when
18
29
  * there is nothing to record. Never throws.
19
30
  */
20
- export declare function redactArgsToJson(args: unknown): string | null;
31
+ export declare function redactArgsToJson(args: unknown, limits?: RedactLimits): string | null;
32
+ /**
33
+ * Same bounds as `redactArgsToJson`, returned as a structural value for
34
+ * surfaces whose consumers expect an object (transcript `metadata.input`).
35
+ */
36
+ export declare function redactArgsToValue(args: unknown, limits?: RedactLimits): unknown;
37
+ /**
38
+ * Bounded, redacted plain-text summary of a tool result. Oversized text keeps
39
+ * its head and gains an explicit `…(N more chars)` marker, so a reader can
40
+ * always tell a short result from a clipped one.
41
+ */
42
+ export declare function redactTextToSummary(text: string, maxChars?: number): string | null;
21
43
  /** Exposed for tests. */
22
44
  export declare const __test: {
23
45
  looksSecret: typeof looksSecret;
24
46
  redact: typeof redact;
47
+ redactEmbeddedSecrets: typeof redactEmbeddedSecrets;
25
48
  SENSITIVE_KEY: RegExp;
26
49
  };
27
50
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/audit/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAYH,8DAA8D;AAC9D,iBAAS,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAkB3C;AAUD,iBAAS,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA0BtD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAqB7D;AAED,yBAAyB;AACzB,eAAO,MAAM,MAAM;;;;CAAyC,CAAC"}
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/audit/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAiBH,8DAA8D;AAC9D,iBAAS,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAkB3C;AAUD,iBAAS,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED,iBAAS,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CA8BzE;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,YAAY,GACpB,MAAM,GAAG,IAAI,CA+Bf;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAQT;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,SAAa,GACpB,MAAM,GAAG,IAAI,CAYf;AAED,yBAAyB;AACzB,eAAO,MAAM,MAAM;;;;;CAKlB,CAAC"}
@@ -10,13 +10,18 @@
10
10
  * This mirrors the framework's standing rule that credential-looking literals
11
11
  * never land in source, logs, or fixtures.
12
12
  */
13
- const SENSITIVE_KEY = /(pass(word|phrase)?|secret|token|api[_-]?key|apikey|authorization|bearer|credential|cookie|session[_-]?(id|token)|private[_-]?key|client[_-]?secret|signing[_-]?secret|access[_-]?key|refresh[_-]?token|webhook[_-]?(url|secret))/i;
13
+ const SENSITIVE_KEY = /(?:pass(?:word|phrase)?|secret|token|api[_-]?key|apikey|authorization|bearer|credential|cookie|session[_-]?(?:id|token)|private[_-]?key|client[_-]?secret|signing[_-]?secret|access[_-]?key|refresh[_-]?token|webhook[_-]?(?:url|secret))/i;
14
14
  const REDACTED = "[redacted]";
15
15
  const MAX_STRING = 2000;
16
16
  const MAX_DEPTH = 6;
17
17
  const MAX_KEYS = 100;
18
18
  const MAX_ARRAY = 100;
19
19
  const MAX_JSON = 8000;
20
+ /**
21
+ * Head room reserved for the `…(N more chars)` marker so a truncated summary
22
+ * still fits its caller's cap. Callers below this cannot carry a marker at all.
23
+ */
24
+ const MARKER_SLACK = 32;
20
25
  /** Heuristic: does a bare string value look like a secret? */
21
26
  function looksSecret(value) {
22
27
  if (/^bearer\s+\S/i.test(value))
@@ -35,25 +40,32 @@ function looksSecret(value) {
35
40
  }
36
41
  return false;
37
42
  }
38
- function redactString(value) {
43
+ function redactString(value, maxString) {
39
44
  if (looksSecret(value))
40
45
  return REDACTED;
41
- if (value.length > MAX_STRING) {
42
- return `${value.slice(0, MAX_STRING)}…(${value.length - MAX_STRING} more chars)`;
46
+ if (value.length > maxString) {
47
+ return `${value.slice(0, maxString)}…(${value.length - maxString} more chars)`;
43
48
  }
44
49
  return value;
45
50
  }
46
- function redact(value, depth) {
51
+ function redactEmbeddedSecrets(value) {
52
+ const bearer = value.replace(/\b(bearer\s+)([^\s"',}]+)/gi, `$1${REDACTED}`);
53
+ const credentialField = new RegExp(`(["']?\\b${SENSITIVE_KEY.source}\\b["']?\\s*[:=]\\s*)(["']?)(?!bearer\\b)([^"'\\s,}]+)\\2`, "gi");
54
+ return bearer.replace(credentialField, `$1$2${REDACTED}$2`);
55
+ }
56
+ function redact(value, depth, maxString) {
47
57
  if (value == null)
48
58
  return value;
49
59
  if (typeof value === "string")
50
- return redactString(value);
60
+ return redactString(value, maxString);
51
61
  if (typeof value === "number" || typeof value === "boolean")
52
62
  return value;
53
63
  if (depth >= MAX_DEPTH)
54
64
  return "[…]";
55
65
  if (Array.isArray(value)) {
56
- const out = value.slice(0, MAX_ARRAY).map((v) => redact(v, depth + 1));
66
+ const out = value
67
+ .slice(0, MAX_ARRAY)
68
+ .map((v) => redact(v, depth + 1, maxString));
57
69
  if (value.length > MAX_ARRAY)
58
70
  out.push(`…(${value.length - MAX_ARRAY} more)`);
59
71
  return out;
@@ -67,7 +79,9 @@ function redact(value, depth) {
67
79
  break;
68
80
  }
69
81
  n += 1;
70
- out[k] = SENSITIVE_KEY.test(k) ? REDACTED : redact(v, depth + 1);
82
+ out[k] = SENSITIVE_KEY.test(k)
83
+ ? REDACTED
84
+ : redact(v, depth + 1, maxString);
71
85
  }
72
86
  return out;
73
87
  }
@@ -78,32 +92,82 @@ function redact(value, depth) {
78
92
  * Redact and serialize call arguments to a capped JSON string, or `null` when
79
93
  * there is nothing to record. Never throws.
80
94
  */
81
- export function redactArgsToJson(args) {
95
+ export function redactArgsToJson(args, limits) {
96
+ const maxJson = Math.max(MARKER_SLACK * 4, limits?.maxJson ?? MAX_JSON);
97
+ const maxString = limits?.maxString ?? MAX_STRING;
82
98
  try {
83
99
  if (args == null)
84
100
  return null;
85
- const redacted = redact(args, 0);
101
+ const redacted = redact(args, 0, maxString);
86
102
  if (redacted === undefined)
87
103
  return null;
88
104
  const json = JSON.stringify(redacted);
89
105
  if (json == null)
90
106
  return null;
91
- if (json.length > MAX_JSON) {
92
- // Slicing the serialized JSON would yield an unparseable string. Wrap a
93
- // preview in a valid envelope so `get-audit-event` can always JSON.parse
94
- // the stored `input`.
95
- return JSON.stringify({
96
- _auditTruncated: true,
97
- originalBytes: json.length,
98
- preview: json.slice(0, MAX_JSON),
99
- });
107
+ if (json.length <= maxJson)
108
+ return json;
109
+ // Slicing the serialized JSON would yield an unparseable string. Wrap a
110
+ // preview in a valid envelope so `get-audit-event` can always JSON.parse
111
+ // the stored `input`. JSON escaping makes the envelope overhead
112
+ // content-dependent, so shrink the preview until the whole thing measures
113
+ // under the cap rather than guessing at the overhead.
114
+ const envelope = (preview) => JSON.stringify({
115
+ _auditTruncated: true,
116
+ originalBytes: json.length,
117
+ preview,
118
+ });
119
+ let take = maxJson;
120
+ let out = envelope(json.slice(0, take));
121
+ while (out.length > maxJson && take > 0) {
122
+ take = Math.max(0, take - Math.max(1, out.length - maxJson));
123
+ out = envelope(json.slice(0, take));
100
124
  }
101
- return json;
125
+ return out;
102
126
  }
103
127
  catch {
104
128
  return null;
105
129
  }
106
130
  }
131
+ /**
132
+ * Same bounds as `redactArgsToJson`, returned as a structural value for
133
+ * surfaces whose consumers expect an object (transcript `metadata.input`).
134
+ */
135
+ export function redactArgsToValue(args, limits) {
136
+ const json = redactArgsToJson(args, limits);
137
+ if (json == null)
138
+ return null;
139
+ try {
140
+ return JSON.parse(json);
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }
146
+ /**
147
+ * Bounded, redacted plain-text summary of a tool result. Oversized text keeps
148
+ * its head and gains an explicit `…(N more chars)` marker, so a reader can
149
+ * always tell a short result from a clipped one.
150
+ */
151
+ export function redactTextToSummary(text, maxChars = MAX_STRING) {
152
+ if (!text)
153
+ return null;
154
+ // Only ever walk a bounded head: a multi-megabyte tool result must not be
155
+ // scanned in full just to produce a preview of it.
156
+ const head = text.slice(0, maxChars + MARKER_SLACK);
157
+ const redactedHead = redactEmbeddedSecrets(head);
158
+ if (looksSecret(redactedHead.trim()))
159
+ return REDACTED;
160
+ if (text.length <= maxChars && redactedHead.length <= maxChars) {
161
+ return redactedHead;
162
+ }
163
+ const keep = Math.max(0, maxChars - MARKER_SLACK);
164
+ return `${redactedHead.slice(0, keep)}…(${text.length - keep} more chars)`;
165
+ }
107
166
  /** Exposed for tests. */
108
- export const __test = { looksSecret, redact, SENSITIVE_KEY };
167
+ export const __test = {
168
+ looksSecret,
169
+ redact,
170
+ redactEmbeddedSecrets,
171
+ SENSITIVE_KEY,
172
+ };
109
173
  //# sourceMappingURL=redact.js.map