@agent-native/core 0.77.8 → 0.77.11

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 (216) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +35 -0
  3. package/corpus/core/package.json +3 -3
  4. package/corpus/core/src/a2a/task-store.ts +25 -3
  5. package/corpus/core/src/agent/harness/store.ts +89 -55
  6. package/corpus/core/src/agent/observational-memory/store.ts +40 -14
  7. package/corpus/core/src/agent/run-store.ts +116 -59
  8. package/corpus/core/src/application-state/store.ts +36 -2
  9. package/corpus/core/src/audit/store.ts +37 -6
  10. package/corpus/core/src/browser-sessions/store.ts +31 -12
  11. package/corpus/core/src/chat-threads/store.ts +67 -3
  12. package/corpus/core/src/checkpoints/store.ts +14 -3
  13. package/corpus/core/src/cli/create.ts +9 -0
  14. package/corpus/core/src/collab/awareness.ts +12 -9
  15. package/corpus/core/src/collab/client.ts +4 -2
  16. package/corpus/core/src/collab/storage.ts +20 -2
  17. package/corpus/core/src/db/ddl-guard.ts +112 -0
  18. package/corpus/core/src/extensions/store.ts +76 -3
  19. package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
  20. package/corpus/core/src/integrations/config-store.ts +19 -18
  21. package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
  22. package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
  23. package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
  24. package/corpus/core/src/integrations/remote-push-store.ts +75 -35
  25. package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
  26. package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
  27. package/corpus/core/src/mcp/connect-store.ts +53 -16
  28. package/corpus/core/src/mcp/oauth-store.ts +32 -7
  29. package/corpus/core/src/notifications/store.ts +20 -4
  30. package/corpus/core/src/oauth-tokens/store.ts +45 -2
  31. package/corpus/core/src/observability/store.ts +154 -47
  32. package/corpus/core/src/progress/store.ts +36 -21
  33. package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
  34. package/corpus/core/src/provider-api/custom-registry.ts +15 -4
  35. package/corpus/core/src/provider-api/quota-governor.ts +15 -3
  36. package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
  37. package/corpus/core/src/resources/store.ts +66 -31
  38. package/corpus/core/src/secrets/storage.ts +18 -22
  39. package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
  40. package/corpus/core/src/server/auth.ts +22 -4
  41. package/corpus/core/src/server/better-auth-instance.ts +49 -15
  42. package/corpus/core/src/server/embed-session.ts +19 -4
  43. package/corpus/core/src/server/identity-sso-store.ts +40 -11
  44. package/corpus/core/src/server/recap-image-store.ts +28 -9
  45. package/corpus/core/src/settings/store.ts +14 -19
  46. package/corpus/core/src/usage/store.ts +51 -16
  47. package/corpus/core/src/workspace-connections/store.ts +200 -22
  48. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
  49. package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
  50. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  51. package/corpus/templates/content/actions/list-trashed-content-databases.ts +36 -2
  52. package/corpus/templates/content/actions/move-document.ts +79 -33
  53. package/corpus/templates/content/actions/restore-content-database.ts +40 -3
  54. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +84 -2
  55. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +177 -49
  56. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +393 -219
  57. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +632 -467
  58. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +114 -14
  59. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +56 -15
  60. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +6 -5
  61. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +12 -4
  62. package/corpus/templates/content/app/global.css +9 -0
  63. package/corpus/templates/content/app/hooks/use-create-page.ts +36 -7
  64. package/corpus/templates/content/app/i18n-data.ts +140 -10
  65. package/corpus/templates/content/changelog/2026-06-25-comment-cards-now-track-their-highlighted-text-while-scrolli.md +6 -0
  66. package/corpus/templates/content/changelog/2026-06-25-comments-now-scroll-with-the-document-and-stay-below-the-pag.md +6 -0
  67. package/corpus/templates/content/changelog/2026-06-25-creating-a-database-from-the-slash-menu-no-longer-leaves-sta.md +6 -0
  68. package/corpus/templates/content/changelog/2026-06-25-sidebar-hover-controls-no-longer-leave-a-lingering-fade-when.md +6 -0
  69. package/corpus/templates/content/changelog/2026-06-25-sidebar-page-actions-are-now-easier-to-see-when-hovering-ina.md +6 -0
  70. package/corpus/templates/content/changelog/2026-06-25-text-selections-in-the-editor-no-longer-show-white-gaps-when.md +6 -0
  71. package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-has-a-one-click-page-link-copy-button.md +6 -0
  72. package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-shows-page-breadcrumbs-and-the-latest.md +6 -0
  73. package/corpus/templates/content/changelog/2026-06-25-the-page-command-now-leaves-a-notion-style-page-reference-an.md +6 -0
  74. package/corpus/templates/content/shared/nfm.ts +16 -0
  75. package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
  76. package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
  77. package/corpus/templates/plan/app/global.css +6 -11
  78. package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
  79. package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
  80. package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
  81. package/dist/a2a/task-store.d.ts.map +1 -1
  82. package/dist/a2a/task-store.js +19 -3
  83. package/dist/a2a/task-store.js.map +1 -1
  84. package/dist/agent/harness/store.d.ts.map +1 -1
  85. package/dist/agent/harness/store.js +63 -34
  86. package/dist/agent/harness/store.js.map +1 -1
  87. package/dist/agent/observational-memory/store.d.ts.map +1 -1
  88. package/dist/agent/observational-memory/store.js +30 -8
  89. package/dist/agent/observational-memory/store.js.map +1 -1
  90. package/dist/agent/run-store.d.ts.map +1 -1
  91. package/dist/agent/run-store.js +103 -46
  92. package/dist/agent/run-store.js.map +1 -1
  93. package/dist/application-state/store.d.ts.map +1 -1
  94. package/dist/application-state/store.js +28 -2
  95. package/dist/application-state/store.js.map +1 -1
  96. package/dist/audit/store.d.ts.map +1 -1
  97. package/dist/audit/store.js +18 -3
  98. package/dist/audit/store.js.map +1 -1
  99. package/dist/browser-sessions/store.d.ts.map +1 -1
  100. package/dist/browser-sessions/store.js +22 -7
  101. package/dist/browser-sessions/store.js.map +1 -1
  102. package/dist/chat-threads/store.d.ts.map +1 -1
  103. package/dist/chat-threads/store.js +51 -3
  104. package/dist/chat-threads/store.js.map +1 -1
  105. package/dist/checkpoints/store.d.ts.map +1 -1
  106. package/dist/checkpoints/store.js +12 -3
  107. package/dist/checkpoints/store.js.map +1 -1
  108. package/dist/cli/create.d.ts.map +1 -1
  109. package/dist/cli/create.js +9 -0
  110. package/dist/cli/create.js.map +1 -1
  111. package/dist/collab/awareness.d.ts +1 -1
  112. package/dist/collab/awareness.d.ts.map +1 -1
  113. package/dist/collab/awareness.js +12 -8
  114. package/dist/collab/awareness.js.map +1 -1
  115. package/dist/collab/client.d.ts.map +1 -1
  116. package/dist/collab/client.js +4 -3
  117. package/dist/collab/client.js.map +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/collab/storage.d.ts.map +1 -1
  120. package/dist/collab/storage.js +14 -2
  121. package/dist/collab/storage.js.map +1 -1
  122. package/dist/db/ddl-guard.d.ts +63 -0
  123. package/dist/db/ddl-guard.d.ts.map +1 -1
  124. package/dist/db/ddl-guard.js +85 -0
  125. package/dist/db/ddl-guard.js.map +1 -1
  126. package/dist/extensions/store.d.ts.map +1 -1
  127. package/dist/extensions/store.js +32 -3
  128. package/dist/extensions/store.js.map +1 -1
  129. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  130. package/dist/integrations/a2a-continuations-store.js +44 -24
  131. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  132. package/dist/integrations/config-store.d.ts.map +1 -1
  133. package/dist/integrations/config-store.js +17 -11
  134. package/dist/integrations/config-store.js.map +1 -1
  135. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  136. package/dist/integrations/pending-tasks-store.js +24 -16
  137. package/dist/integrations/pending-tasks-store.js.map +1 -1
  138. package/dist/integrations/remote-commands-store.d.ts.map +1 -1
  139. package/dist/integrations/remote-commands-store.js +29 -21
  140. package/dist/integrations/remote-commands-store.js.map +1 -1
  141. package/dist/integrations/remote-devices-store.d.ts.map +1 -1
  142. package/dist/integrations/remote-devices-store.js +33 -18
  143. package/dist/integrations/remote-devices-store.js.map +1 -1
  144. package/dist/integrations/remote-push-store.d.ts.map +1 -1
  145. package/dist/integrations/remote-push-store.js +53 -31
  146. package/dist/integrations/remote-push-store.js.map +1 -1
  147. package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
  148. package/dist/integrations/remote-run-events-store.js +26 -10
  149. package/dist/integrations/remote-run-events-store.js.map +1 -1
  150. package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
  151. package/dist/integrations/thread-mapping-store.js +10 -2
  152. package/dist/integrations/thread-mapping-store.js.map +1 -1
  153. package/dist/mcp/connect-store.d.ts.map +1 -1
  154. package/dist/mcp/connect-store.js +34 -16
  155. package/dist/mcp/connect-store.js.map +1 -1
  156. package/dist/mcp/oauth-store.d.ts.map +1 -1
  157. package/dist/mcp/oauth-store.js +22 -7
  158. package/dist/mcp/oauth-store.js.map +1 -1
  159. package/dist/notifications/store.d.ts.map +1 -1
  160. package/dist/notifications/store.js +15 -3
  161. package/dist/notifications/store.js.map +1 -1
  162. package/dist/oauth-tokens/store.d.ts.map +1 -1
  163. package/dist/oauth-tokens/store.js +33 -2
  164. package/dist/oauth-tokens/store.js.map +1 -1
  165. package/dist/observability/store.d.ts.map +1 -1
  166. package/dist/observability/store.js +72 -29
  167. package/dist/observability/store.js.map +1 -1
  168. package/dist/progress/store.d.ts.map +1 -1
  169. package/dist/progress/store.js +31 -16
  170. package/dist/progress/store.js.map +1 -1
  171. package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
  172. package/dist/provider-api/corpus-jobs-store.js +16 -5
  173. package/dist/provider-api/corpus-jobs-store.js.map +1 -1
  174. package/dist/provider-api/custom-registry.d.ts.map +1 -1
  175. package/dist/provider-api/custom-registry.js +14 -4
  176. package/dist/provider-api/custom-registry.js.map +1 -1
  177. package/dist/provider-api/quota-governor.d.ts.map +1 -1
  178. package/dist/provider-api/quota-governor.js +12 -3
  179. package/dist/provider-api/quota-governor.js.map +1 -1
  180. package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
  181. package/dist/provider-api/staged-datasets-store.js +16 -5
  182. package/dist/provider-api/staged-datasets-store.js.map +1 -1
  183. package/dist/resources/store.d.ts.map +1 -1
  184. package/dist/resources/store.js +56 -25
  185. package/dist/resources/store.js.map +1 -1
  186. package/dist/secrets/storage.d.ts.map +1 -1
  187. package/dist/secrets/storage.js +10 -14
  188. package/dist/secrets/storage.js.map +1 -1
  189. package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
  190. package/dist/server/agent-teams-run-queue.js +15 -4
  191. package/dist/server/agent-teams-run-queue.js.map +1 -1
  192. package/dist/server/auth.d.ts.map +1 -1
  193. package/dist/server/auth.js +16 -2
  194. package/dist/server/auth.js.map +1 -1
  195. package/dist/server/better-auth-instance.d.ts.map +1 -1
  196. package/dist/server/better-auth-instance.js +55 -21
  197. package/dist/server/better-auth-instance.js.map +1 -1
  198. package/dist/server/embed-session.d.ts.map +1 -1
  199. package/dist/server/embed-session.js +15 -4
  200. package/dist/server/embed-session.js.map +1 -1
  201. package/dist/server/identity-sso-store.d.ts.map +1 -1
  202. package/dist/server/identity-sso-store.js +30 -11
  203. package/dist/server/identity-sso-store.js.map +1 -1
  204. package/dist/server/recap-image-store.d.ts.map +1 -1
  205. package/dist/server/recap-image-store.js +21 -7
  206. package/dist/server/recap-image-store.js.map +1 -1
  207. package/dist/settings/store.d.ts.map +1 -1
  208. package/dist/settings/store.js +11 -13
  209. package/dist/settings/store.js.map +1 -1
  210. package/dist/usage/store.d.ts.map +1 -1
  211. package/dist/usage/store.js +36 -11
  212. package/dist/usage/store.js.map +1 -1
  213. package/dist/workspace-connections/store.d.ts.map +1 -1
  214. package/dist/workspace-connections/store.js +57 -7
  215. package/dist/workspace-connections/store.js.map +1 -1
  216. package/package.json +3 -3
@@ -24,7 +24,13 @@
24
24
 
25
25
  import { randomBytes } from "node:crypto";
26
26
 
27
- import { getDbExec, isConnectionError, intType } from "../db/client.js";
27
+ import {
28
+ getDbExec,
29
+ isConnectionError,
30
+ intType,
31
+ isPostgres,
32
+ } from "../db/client.js";
33
+ import { ensureTableExists } from "../db/ddl-guard.js";
28
34
 
29
35
  let _initPromise: Promise<void> | undefined;
30
36
 
@@ -98,13 +104,11 @@ export const SSO_STATE_TTL_MS = 10 * 60_000;
98
104
  export const SSO_LOGIN_MAX = 60;
99
105
  export const SSO_LOGIN_WINDOW_MS = 60_000;
100
106
 
101
- async function ensureTable(): Promise<void> {
102
- if (!_initPromise) {
103
- _initPromise = (async () => {
104
- const client = getDbExec();
105
- // Additive only. Never DROP / ALTER — this DB is shared across every
106
- // deploy context (preview/branch/prod) for hosted templates.
107
- await client.execute(`
107
+ // Build the CREATE SQL lazily (not at module scope) so intType() runs at
108
+ // RUNTIME, not import time — a module-scope call breaks any consumer whose
109
+ // db/client mock doesn't stub intType (e.g. db-admin specs).
110
+ function buildIdentitySsoStateCreateSql(): string {
111
+ return `
108
112
  CREATE TABLE IF NOT EXISTS identity_sso_state (
109
113
  state TEXT PRIMARY KEY,
110
114
  return_path TEXT,
@@ -112,13 +116,38 @@ async function ensureTable(): Promise<void> {
112
116
  expires_at ${intType()},
113
117
  consumed_at ${intType()}
114
118
  )
115
- `);
116
- await client.execute(`
119
+ `;
120
+ }
121
+ function buildIdentitySsoJtiCreateSql(): string {
122
+ return `
117
123
  CREATE TABLE IF NOT EXISTS identity_sso_jti (
118
124
  jti TEXT PRIMARY KEY,
119
125
  seen_at ${intType()}
120
126
  )
121
- `);
127
+ `;
128
+ }
129
+
130
+ async function ensureTable(): Promise<void> {
131
+ if (!_initPromise) {
132
+ _initPromise = (async () => {
133
+ const client = getDbExec();
134
+ const identitySsoStateCreateSql = buildIdentitySsoStateCreateSql();
135
+ const identitySsoJtiCreateSql = buildIdentitySsoJtiCreateSql();
136
+ // Additive only. Never DROP / ALTER — this DB is shared across every
137
+ // deploy context (preview/branch/prod) for hosted templates.
138
+ if (isPostgres()) {
139
+ // PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
140
+ await ensureTableExists(
141
+ "identity_sso_state",
142
+ identitySsoStateCreateSql,
143
+ );
144
+ await ensureTableExists("identity_sso_jti", identitySsoJtiCreateSql);
145
+ return;
146
+ }
147
+
148
+ // SQLite (local dev): no lock problem — keep the original behaviour.
149
+ await client.execute(identitySsoStateCreateSql);
150
+ await client.execute(identitySsoJtiCreateSql);
122
151
  })().catch((err) => {
123
152
  // Don't cache a rejection — let the next caller retry a fresh init.
124
153
  _initPromise = undefined;
@@ -19,7 +19,13 @@
19
19
  */
20
20
  import { randomBytes } from "node:crypto";
21
21
 
22
- import { getDbExec, intType, retryOnDdlRace } from "../db/client.js";
22
+ import {
23
+ getDbExec,
24
+ intType,
25
+ isPostgres,
26
+ retryOnDdlRace,
27
+ } from "../db/client.js";
28
+ import { ensureTableExists } from "../db/ddl-guard.js";
23
29
 
24
30
  /** Maximum stored image size (~5 MB of raw PNG bytes). */
25
31
  export const RECAP_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
@@ -44,12 +50,11 @@ const TOKEN_PATTERN = /^[0-9a-f]{32,128}$/;
44
50
 
45
51
  let _initPromise: Promise<void> | undefined;
46
52
 
47
- export async function ensureRecapImageTable(): Promise<void> {
48
- if (!_initPromise) {
49
- _initPromise = (async () => {
50
- const client = getDbExec();
51
- await retryOnDdlRace(() =>
52
- client.execute(`
53
+ // Build the CREATE SQL lazily (not at module scope) so intType() runs at
54
+ // RUNTIME, not import time — a module-scope call breaks any consumer whose
55
+ // db/client mock doesn't stub intType (e.g. db-admin specs).
56
+ function buildRecapImagesCreateSql(): string {
57
+ return `
53
58
  CREATE TABLE IF NOT EXISTS recap_images (
54
59
  token TEXT PRIMARY KEY,
55
60
  png_base64 TEXT NOT NULL,
@@ -58,8 +63,22 @@ export async function ensureRecapImageTable(): Promise<void> {
58
63
  owner_email TEXT,
59
64
  created_at ${intType()} NOT NULL
60
65
  )
61
- `),
62
- );
66
+ `;
67
+ }
68
+
69
+ export async function ensureRecapImageTable(): Promise<void> {
70
+ if (!_initPromise) {
71
+ _initPromise = (async () => {
72
+ const recapImagesCreateSql = buildRecapImagesCreateSql();
73
+ if (isPostgres()) {
74
+ // PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
75
+ await ensureTableExists("recap_images", recapImagesCreateSql);
76
+ return;
77
+ }
78
+
79
+ // SQLite (local dev): no lock problem — keep the original behaviour.
80
+ const client = getDbExec();
81
+ await retryOnDdlRace(() => client.execute(recapImagesCreateSql));
63
82
  })().catch((error) => {
64
83
  // Allow a later call to retry if the first init lost a DDL race.
65
84
  _initPromise = undefined;
@@ -1,11 +1,7 @@
1
1
  import { EventEmitter } from "events";
2
2
 
3
3
  import { getDbExec, isPostgres, intType } from "../db/client.js";
4
- import {
5
- pgIndexExists,
6
- pgTableExists,
7
- runGuardedDdl,
8
- } from "../db/ddl-guard.js";
4
+ import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
9
5
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
10
6
 
11
7
  let _initPromise: Promise<void> | undefined;
@@ -39,26 +35,25 @@ async function ensureTable(): Promise<void> {
39
35
  // `CREATE INDEX` still takes a lock that, in a fresh background-worker
40
36
  // process behind a concurrent connection on the shared Neon DB, can
41
37
  // block ~indefinitely (ACCESS EXCLUSIVE for CREATE TABLE; a write-
42
- // blocking SHARE lock for CREATE INDEX). So check `information_schema`/
43
- // `pg_indexes` first (plain reads, no lock) and run DDL ONLY for what
44
- // is actually missing. `runGuardedDdl` bounds any DDL that must run
45
- // with a transaction-scoped `lock_timeout` so a contended lock fails
46
- // fast instead of hanging. `settingsTable()` is `public.settings` on
47
- // Postgres; the existence checks use the unqualified table name.
48
- if (!(await pgTableExists("settings"))) {
49
- await runGuardedDdl(createSql);
50
- }
38
+ // blocking SHARE lock for CREATE INDEX). `ensureTableExists` /
39
+ // `ensureIndexExists` probe `information_schema`/`pg_indexes` first
40
+ // (plain reads, no lock) and run DDL ONLY for what is actually missing,
41
+ // bounding any DDL with a transaction-scoped `lock_timeout`. They also
42
+ // re-probe after a swallowed lock-timeout and THROW if the schema is
43
+ // still missing, so a timed-out DDL never poisons this init memo with
44
+ // missing schema. `settingsTable()` is `public.settings` on Postgres;
45
+ // the existence checks use the unqualified table name.
46
+ await ensureTableExists("settings", createSql);
51
47
  // Older deployments (pre BIGINT-compat) have a 32-bit `updated_at`; on
52
48
  // Postgres the `Date.now()` written on every setSetting overflows int4.
53
49
  // widenIntColumnsToBigInt already probes information_schema and only
54
50
  // ALTERs columns that are still int4 — a no-op on fresh/widened DBs.
55
51
  await widenIntColumnsToBigInt("settings", ["updated_at"]);
56
52
  // Index for the poll watermark query: `SELECT MAX(updated_at)`.
57
- if (!(await pgIndexExists("settings_updated_at_idx"))) {
58
- await runGuardedDdl(
59
- `CREATE INDEX IF NOT EXISTS settings_updated_at_idx ON ${table} (updated_at)`,
60
- );
61
- }
53
+ await ensureIndexExists(
54
+ "settings_updated_at_idx",
55
+ `CREATE INDEX IF NOT EXISTS settings_updated_at_idx ON ${table} (updated_at)`,
56
+ );
62
57
  return;
63
58
  }
64
59
 
@@ -8,6 +8,11 @@
8
8
  * Cost is stored as "centicents" (1/100th of a cent) for integer precision.
9
9
  */
10
10
  import { getDbExec, intType, isPostgres } from "../db/client.js";
11
+ import {
12
+ ensureColumnExists,
13
+ ensureIndexExists,
14
+ ensureTableExists,
15
+ } from "../db/ddl-guard.js";
11
16
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
12
17
 
13
18
  /**
@@ -191,7 +196,7 @@ async function ensureUsageTable(): Promise<void> {
191
196
  if (!_initPromise) {
192
197
  _initPromise = (async () => {
193
198
  const client = getDbExec();
194
- await client.execute(`
199
+ const createSql = `
195
200
  CREATE TABLE IF NOT EXISTS token_usage (
196
201
  id ${intType()} PRIMARY KEY,
197
202
  owner_email TEXT NOT NULL,
@@ -206,11 +211,9 @@ async function ensureUsageTable(): Promise<void> {
206
211
  ref_id TEXT NOT NULL DEFAULT '',
207
212
  created_at ${intType()} NOT NULL
208
213
  )
209
- `);
214
+ `;
210
215
 
211
- // Add columns on older deployments that pre-date the label/cache
212
- // fields. Each ALTER is wrapped so a dialect without IF NOT EXISTS
213
- // (SQLite) still makes progress if only some columns are missing.
216
+ // Additive columns for older deployments that pre-date the label/cache fields.
214
217
  const additions: Array<[string, string]> = [
215
218
  ["cache_read_tokens", `${intType()} NOT NULL DEFAULT 0`],
216
219
  ["cache_write_tokens", `${intType()} NOT NULL DEFAULT 0`],
@@ -218,27 +221,59 @@ async function ensureUsageTable(): Promise<void> {
218
221
  ["app", `TEXT NOT NULL DEFAULT ''`],
219
222
  ["ref_id", `TEXT NOT NULL DEFAULT ''`],
220
223
  ];
224
+
225
+ if (isPostgres()) {
226
+ // Hot path: the `token_usage` table and its index are virtually always
227
+ // already present in production. Issuing `CREATE TABLE`/`ALTER TABLE`/
228
+ // `CREATE INDEX` still takes a lock that, in a fresh background-worker
229
+ // process behind a concurrent connection on the shared Neon DB, can
230
+ // block ~indefinitely. The ensure* wrappers probe `information_schema`/
231
+ // `pg_indexes` first (plain reads, no lock) and run DDL ONLY for what is
232
+ // actually missing, bounded by a transaction-scoped `lock_timeout`. If a
233
+ // swallowed lock-timeout leaves the schema still missing they RE-PROBE
234
+ // and THROW rather than letting init memoize success against absent
235
+ // schema.
236
+ await ensureTableExists("token_usage", createSql);
237
+ // Add columns on older deployments — guarded so the hot path (columns
238
+ // already present) skips the ACCESS EXCLUSIVE ALTER.
239
+ for (const [col, def] of additions) {
240
+ await ensureColumnExists(
241
+ "token_usage",
242
+ col,
243
+ `ALTER TABLE token_usage ADD COLUMN IF NOT EXISTS ${col} ${def}`,
244
+ );
245
+ }
246
+ // Older deployments created `created_at` as 32-bit `INTEGER`; on Postgres
247
+ // the `Date.now()` written per run by recordUsage() overflows int4. Widen
248
+ // it in place (no-op once done / on fresh BIGINT databases).
249
+ await widenIntColumnsToBigInt("token_usage", ["created_at"]);
250
+ // Probe pg_indexes first (no lock) and skip the SHARE-locking CREATE
251
+ // INDEX when the index is already present.
252
+ await ensureIndexExists(
253
+ "idx_token_usage_owner_created",
254
+ `CREATE INDEX IF NOT EXISTS idx_token_usage_owner_created ON token_usage (owner_email, created_at)`,
255
+ );
256
+ return;
257
+ }
258
+
259
+ // SQLite (local dev): no lock problem — keep the original behaviour.
260
+ await client.execute(createSql);
261
+ // Add columns on older deployments that pre-date the label/cache
262
+ // fields. Each ALTER is wrapped so a dialect without IF NOT EXISTS
263
+ // (SQLite) still makes progress if only some columns are missing.
221
264
  for (const [col, def] of additions) {
222
265
  try {
223
- if (isPostgres()) {
224
- await client.execute(
225
- `ALTER TABLE token_usage ADD COLUMN IF NOT EXISTS ${col} ${def}`,
226
- );
227
- } else {
228
- await client.execute(
229
- `ALTER TABLE token_usage ADD COLUMN ${col} ${def}`,
230
- );
231
- }
266
+ await client.execute(
267
+ `ALTER TABLE token_usage ADD COLUMN ${col} ${def}`,
268
+ );
232
269
  } catch {
233
270
  // Column already exists — ignore
234
271
  }
235
272
  }
236
-
237
273
  // Older deployments created `created_at` as 32-bit `INTEGER`; on Postgres
238
274
  // the `Date.now()` written per run by recordUsage() overflows int4. Widen
239
275
  // it in place (no-op once done / on fresh BIGINT databases).
240
276
  await widenIntColumnsToBigInt("token_usage", ["created_at"]);
241
-
242
277
  try {
243
278
  await client.execute(
244
279
  `CREATE INDEX IF NOT EXISTS idx_token_usage_owner_created ON token_usage (owner_email, created_at)`,
@@ -15,6 +15,11 @@ import {
15
15
  safeJsonParse,
16
16
  type DbExec,
17
17
  } from "../db/client.js";
18
+ import {
19
+ ensureColumnExists,
20
+ ensureIndexExists,
21
+ ensureTableExists,
22
+ } from "../db/ddl-guard.js";
18
23
  import {
19
24
  getRequestOrgId,
20
25
  getRequestUserEmail,
@@ -459,8 +464,8 @@ export async function ensureWorkspaceConnectionsTable(): Promise<void> {
459
464
  const client = getDbExec();
460
465
  const table = workspaceConnectionsTable();
461
466
  const grantsTable = workspaceConnectionGrantsTable();
462
- await retryOnDdlRace(() =>
463
- client.execute(`
467
+
468
+ const createConnectionsSql = `
464
469
  CREATE TABLE IF NOT EXISTS ${table} (
465
470
  id TEXT PRIMARY KEY,
466
471
  provider TEXT NOT NULL DEFAULT '',
@@ -480,23 +485,8 @@ export async function ensureWorkspaceConnectionsTable(): Promise<void> {
480
485
  last_checked_at ${intType()},
481
486
  last_error TEXT
482
487
  )
483
- `),
484
- );
485
-
486
- await ensureWorkspaceConnectionColumns(client, table);
487
-
488
- await retryOnDdlRace(() =>
489
- client.execute(
490
- `CREATE INDEX IF NOT EXISTS idx_workspace_connections_scope_provider ON ${table} (org_id, owner_email, provider)`,
491
- ),
492
- );
493
- await retryOnDdlRace(() =>
494
- client.execute(
495
- `CREATE INDEX IF NOT EXISTS idx_workspace_connections_updated_at ON ${table} (updated_at)`,
496
- ),
497
- );
498
- await retryOnDdlRace(() =>
499
- client.execute(`
488
+ `;
489
+ const createGrantsSql = `
500
490
  CREATE TABLE IF NOT EXISTS ${grantsTable} (
501
491
  id TEXT PRIMARY KEY,
502
492
  connection_id TEXT NOT NULL DEFAULT '',
@@ -512,11 +502,199 @@ export async function ensureWorkspaceConnectionsTable(): Promise<void> {
512
502
  updated_at ${intType()} NOT NULL DEFAULT 0,
513
503
  last_used_at ${intType()}
514
504
  )
515
- `),
516
- );
505
+ `;
506
+
507
+ if (isPostgres()) {
508
+ // PG-guard: probe information_schema / pg_indexes first (no lock) and
509
+ // only issue DDL when the table/column/index is actually missing,
510
+ // wrapped in a transaction-scoped lock_timeout so a contended lock
511
+ // fails fast. Uses unqualified table names for the probe (the helpers
512
+ // always search the `public` schema).
513
+
514
+ // --- workspace_connections ---
515
+ await ensureTableExists("workspace_connections", createConnectionsSql);
516
+ await ensureColumnExists(
517
+ "workspace_connections",
518
+ "provider",
519
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS provider TEXT NOT NULL DEFAULT ''`,
520
+ );
521
+ await ensureColumnExists(
522
+ "workspace_connections",
523
+ "label",
524
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS label TEXT NOT NULL DEFAULT ''`,
525
+ );
526
+ await ensureColumnExists(
527
+ "workspace_connections",
528
+ "account_id",
529
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS account_id TEXT`,
530
+ );
531
+ await ensureColumnExists(
532
+ "workspace_connections",
533
+ "account_label",
534
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS account_label TEXT`,
535
+ );
536
+ await ensureColumnExists(
537
+ "workspace_connections",
538
+ "status",
539
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'connected'`,
540
+ );
541
+ await ensureColumnExists(
542
+ "workspace_connections",
543
+ "scopes_json",
544
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS scopes_json TEXT NOT NULL DEFAULT '[]'`,
545
+ );
546
+ await ensureColumnExists(
547
+ "workspace_connections",
548
+ "config_json",
549
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS config_json TEXT NOT NULL DEFAULT '{}'`,
550
+ );
551
+ await ensureColumnExists(
552
+ "workspace_connections",
553
+ "allowed_apps_json",
554
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS allowed_apps_json TEXT NOT NULL DEFAULT '[]'`,
555
+ );
556
+ await ensureColumnExists(
557
+ "workspace_connections",
558
+ "credential_refs_json",
559
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS credential_refs_json TEXT NOT NULL DEFAULT '[]'`,
560
+ );
561
+ await ensureColumnExists(
562
+ "workspace_connections",
563
+ "owner_email",
564
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS owner_email TEXT NOT NULL DEFAULT ''`,
565
+ );
566
+ await ensureColumnExists(
567
+ "workspace_connections",
568
+ "org_id",
569
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS org_id TEXT`,
570
+ );
571
+ await ensureColumnExists(
572
+ "workspace_connections",
573
+ "created_at",
574
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS created_at ${intType()} NOT NULL DEFAULT 0`,
575
+ );
576
+ await ensureColumnExists(
577
+ "workspace_connections",
578
+ "updated_at",
579
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS updated_at ${intType()} NOT NULL DEFAULT 0`,
580
+ );
581
+ await ensureColumnExists(
582
+ "workspace_connections",
583
+ "last_checked_at",
584
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS last_checked_at ${intType()}`,
585
+ );
586
+ await ensureColumnExists(
587
+ "workspace_connections",
588
+ "last_used_at",
589
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS last_used_at ${intType()}`,
590
+ );
591
+ await ensureColumnExists(
592
+ "workspace_connections",
593
+ "last_error",
594
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS last_error TEXT`,
595
+ );
596
+ await ensureIndexExists(
597
+ "idx_workspace_connections_scope_provider",
598
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connections_scope_provider ON ${table} (org_id, owner_email, provider)`,
599
+ );
600
+ await ensureIndexExists(
601
+ "idx_workspace_connections_updated_at",
602
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connections_updated_at ON ${table} (updated_at)`,
603
+ );
517
604
 
518
- await ensureWorkspaceConnectionGrantColumns(client, grantsTable);
605
+ // --- workspace_connection_grants ---
606
+ await ensureTableExists("workspace_connection_grants", createGrantsSql);
607
+ await ensureColumnExists(
608
+ "workspace_connection_grants",
609
+ "connection_id",
610
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS connection_id TEXT NOT NULL DEFAULT ''`,
611
+ );
612
+ await ensureColumnExists(
613
+ "workspace_connection_grants",
614
+ "provider",
615
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS provider TEXT NOT NULL DEFAULT ''`,
616
+ );
617
+ await ensureColumnExists(
618
+ "workspace_connection_grants",
619
+ "app_id",
620
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS app_id TEXT NOT NULL DEFAULT ''`,
621
+ );
622
+ await ensureColumnExists(
623
+ "workspace_connection_grants",
624
+ "scopes_json",
625
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS scopes_json TEXT NOT NULL DEFAULT '[]'`,
626
+ );
627
+ await ensureColumnExists(
628
+ "workspace_connection_grants",
629
+ "config_json",
630
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS config_json TEXT NOT NULL DEFAULT '{}'`,
631
+ );
632
+ await ensureColumnExists(
633
+ "workspace_connection_grants",
634
+ "credential_refs_json",
635
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS credential_refs_json TEXT NOT NULL DEFAULT '[]'`,
636
+ );
637
+ await ensureColumnExists(
638
+ "workspace_connection_grants",
639
+ "granted_by_email",
640
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS granted_by_email TEXT NOT NULL DEFAULT ''`,
641
+ );
642
+ await ensureColumnExists(
643
+ "workspace_connection_grants",
644
+ "owner_email",
645
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS owner_email TEXT NOT NULL DEFAULT ''`,
646
+ );
647
+ await ensureColumnExists(
648
+ "workspace_connection_grants",
649
+ "org_id",
650
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS org_id TEXT`,
651
+ );
652
+ await ensureColumnExists(
653
+ "workspace_connection_grants",
654
+ "created_at",
655
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS created_at ${intType()} NOT NULL DEFAULT 0`,
656
+ );
657
+ await ensureColumnExists(
658
+ "workspace_connection_grants",
659
+ "updated_at",
660
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS updated_at ${intType()} NOT NULL DEFAULT 0`,
661
+ );
662
+ await ensureColumnExists(
663
+ "workspace_connection_grants",
664
+ "last_used_at",
665
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS last_used_at ${intType()}`,
666
+ );
667
+ await ensureIndexExists(
668
+ "idx_workspace_connection_grants_connection_app",
669
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_connection_grants_connection_app ON ${grantsTable} (connection_id, app_id)`,
670
+ );
671
+ await ensureIndexExists(
672
+ "idx_workspace_connection_grants_scope_app",
673
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connection_grants_scope_app ON ${grantsTable} (org_id, owner_email, app_id)`,
674
+ );
675
+ await ensureIndexExists(
676
+ "idx_workspace_connection_grants_updated_at",
677
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connection_grants_updated_at ON ${grantsTable} (updated_at)`,
678
+ );
679
+ return;
680
+ }
519
681
 
682
+ // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
683
+ // retryOnDdlRace + ensureColumn behaviour.
684
+ await retryOnDdlRace(() => client.execute(createConnectionsSql));
685
+ await ensureWorkspaceConnectionColumns(client, table);
686
+ await retryOnDdlRace(() =>
687
+ client.execute(
688
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connections_scope_provider ON ${table} (org_id, owner_email, provider)`,
689
+ ),
690
+ );
691
+ await retryOnDdlRace(() =>
692
+ client.execute(
693
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connections_updated_at ON ${table} (updated_at)`,
694
+ ),
695
+ );
696
+ await retryOnDdlRace(() => client.execute(createGrantsSql));
697
+ await ensureWorkspaceConnectionGrantColumns(client, grantsTable);
520
698
  await retryOnDdlRace(() =>
521
699
  client.execute(
522
700
  `CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_connection_grants_connection_app ON ${grantsTable} (connection_id, app_id)`,
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Booking link previews now show the content directly on the grid background.
@@ -267,8 +267,6 @@ export function renderBookingOgImageSvg(input: BookingOgImageInput): string {
267
267
  </defs>
268
268
  <rect width="${WIDTH}" height="${HEIGHT}" fill="${BG}"/>
269
269
  <rect width="${WIDTH}" height="${HEIGHT}" fill="url(#grid)"/>
270
- <rect x="64" y="64" width="1072" height="502" rx="28" fill="${BG}" fill-opacity="0.72" stroke="${BORDER}" stroke-width="1"/>
271
- <path d="M80 154 H1120" stroke="${BORDER}"/>
272
270
  <g transform="translate(80 86)">
273
271
  <g transform="scale(0.62)">
274
272
  ${LOGO_MARK}
@@ -11,7 +11,7 @@
11
11
  "package": "pnpm build && tsx scripts/package.ts"
12
12
  },
13
13
  "dependencies": {
14
- "@sentry/browser": "^10.61.0"
14
+ "@sentry/browser": "10.60.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/chrome": "^0.2.0",
@@ -1,6 +1,7 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import { accessFilter } from "@agent-native/core/sharing";
3
- import { and, desc, eq, isNotNull } from "drizzle-orm";
3
+ import { and, desc, eq, isNotNull, isNull, ne, or } from "drizzle-orm";
4
+ import { alias } from "drizzle-orm/sqlite-core";
4
5
  import { z } from "zod";
5
6
 
6
7
  import { getDb, schema } from "../server/db/index.js";
@@ -14,6 +15,16 @@ export default defineAction({
14
15
  readOnly: true,
15
16
  run: async (): Promise<ListTrashedContentDatabasesResponse> => {
16
17
  const db = getDb();
18
+ const hostDocuments = alias(schema.documents, "host_documents");
19
+ const isBlockOwned = and(
20
+ isNotNull(schema.contentDatabases.ownerDocumentId),
21
+ eq(schema.documents.parentId, schema.contentDatabases.ownerDocumentId),
22
+ );
23
+ const isNotBlockOwned = or(
24
+ isNull(schema.contentDatabases.ownerDocumentId),
25
+ isNull(schema.documents.parentId),
26
+ ne(schema.documents.parentId, schema.contentDatabases.ownerDocumentId),
27
+ );
17
28
  const rows = await db
18
29
  .select({
19
30
  databaseId: schema.contentDatabases.id,
@@ -28,10 +39,33 @@ export default defineAction({
28
39
  schema.documents,
29
40
  eq(schema.documents.id, schema.contentDatabases.documentId),
30
41
  )
42
+ .leftJoin(
43
+ hostDocuments,
44
+ eq(hostDocuments.id, schema.contentDatabases.ownerDocumentId),
45
+ )
31
46
  .where(
32
47
  and(
33
48
  isNotNull(schema.contentDatabases.deletedAt),
34
- accessFilter(schema.documents, schema.documentShares),
49
+ or(
50
+ and(
51
+ isBlockOwned,
52
+ accessFilter(
53
+ hostDocuments,
54
+ schema.documentShares,
55
+ undefined,
56
+ "editor",
57
+ ),
58
+ ),
59
+ and(
60
+ isNotBlockOwned,
61
+ accessFilter(
62
+ schema.documents,
63
+ schema.documentShares,
64
+ undefined,
65
+ "admin",
66
+ ),
67
+ ),
68
+ ),
35
69
  ),
36
70
  )
37
71
  .orderBy(desc(schema.contentDatabases.deletedAt));