@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
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { getDbExec, intType, isPostgres, type DbExec } from "../db/client.js";
11
+ import { ensureTableExists, ensureIndexExists } from "../db/ddl-guard.js";
11
12
 
12
13
  export type ProviderCorpusJobStatus =
13
14
  | "running"
@@ -81,7 +82,7 @@ async function ensureTables(): Promise<void> {
81
82
  initPromise = (async () => {
82
83
  const db = getDbExec();
83
84
  const integerType = intType();
84
- await db.execute(`
85
+ const createJobsSql = `
85
86
  CREATE TABLE IF NOT EXISTS provider_corpus_jobs (
86
87
  id TEXT NOT NULL,
87
88
  app_id TEXT NOT NULL,
@@ -108,18 +109,37 @@ async function ensureTables(): Promise<void> {
108
109
  updated_at ${integerType} NOT NULL,
109
110
  PRIMARY KEY (id)
110
111
  )
111
- `);
112
- await db.execute(`
112
+ `;
113
+ const createHitsSql = `
113
114
  CREATE TABLE IF NOT EXISTS provider_corpus_job_hits (
114
115
  job_id TEXT NOT NULL,
115
116
  hit_index ${integerType} NOT NULL,
116
117
  hit_data TEXT NOT NULL,
117
118
  PRIMARY KEY (job_id, hit_index)
118
119
  )
119
- `);
120
+ `;
120
121
  if (isPostgres()) {
121
- await widenPostgresIntegerColumns(db);
122
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
123
+ await ensureTableExists("provider_corpus_jobs", createJobsSql);
124
+ await ensureTableExists("provider_corpus_job_hits", createHitsSql);
125
+ await widenPostgresIntegerColumns(db); // best-effort type widening — unchanged
126
+ await ensureIndexExists(
127
+ "provider_corpus_jobs_scope_idx",
128
+ `CREATE INDEX IF NOT EXISTS provider_corpus_jobs_scope_idx ON provider_corpus_jobs (app_id, owner_email, updated_at)`,
129
+ );
130
+ await ensureIndexExists(
131
+ "provider_corpus_jobs_status_idx",
132
+ `CREATE INDEX IF NOT EXISTS provider_corpus_jobs_status_idx ON provider_corpus_jobs (app_id, owner_email, status)`,
133
+ );
134
+ await ensureIndexExists(
135
+ "provider_corpus_job_hits_job_idx",
136
+ `CREATE INDEX IF NOT EXISTS provider_corpus_job_hits_job_idx ON provider_corpus_job_hits (job_id)`,
137
+ );
138
+ return;
122
139
  }
140
+ // SQLite (local dev): keep existing behavior
141
+ await db.execute(createJobsSql);
142
+ await db.execute(createHitsSql);
123
143
  for (const ddl of [
124
144
  `CREATE INDEX IF NOT EXISTS provider_corpus_jobs_scope_idx ON provider_corpus_jobs (app_id, owner_email, updated_at)`,
125
145
  `CREATE INDEX IF NOT EXISTS provider_corpus_jobs_status_idx ON provider_corpus_jobs (app_id, owner_email, status)`,
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  import { getDbExec, isPostgres } from "../db/client.js";
24
+ import { ensureTableExists } from "../db/ddl-guard.js";
24
25
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
25
26
  import { isBlockedExtensionUrlWithDns } from "../extensions/url-safety.js";
26
27
 
@@ -91,10 +92,20 @@ async function ensureTable(): Promise<void> {
91
92
  if (!_initPromise) {
92
93
  _initPromise = (async () => {
93
94
  const client = getDbExec();
94
- const sql = isPostgres()
95
- ? CREATE_SQL.replace(/\bINTEGER\b/g, "BIGINT")
96
- : CREATE_SQL;
97
- await client.execute(sql);
95
+
96
+ if (isPostgres()) {
97
+ const pgSql = CREATE_SQL.replace(/\bINTEGER\b/g, "BIGINT");
98
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
99
+ await ensureTableExists("custom_api_providers", pgSql);
100
+ // Widen any pre-existing int4 timestamp columns to BIGINT (no-op once done)
101
+ await widenIntColumnsToBigInt("custom_api_providers", [
102
+ "created_at",
103
+ "updated_at",
104
+ ]);
105
+ return;
106
+ }
107
+ // SQLite (local dev): keep existing behavior
108
+ await client.execute(CREATE_SQL);
98
109
  // Fresh Postgres tables get BIGINT via the replace above, but tables
99
110
  // created before that compat existed kept int4 timestamp columns; widen
100
111
  // them so `Date.now()` writes don't overflow. No-op once done.
@@ -1,7 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
 
3
3
  import type { CredentialContext } from "../credentials/index.js";
4
- import { getDbExec, intType } from "../db/client.js";
4
+ import { getDbExec, intType, isPostgres } from "../db/client.js";
5
+ import { ensureTableExists, ensureIndexExists } from "../db/ddl-guard.js";
5
6
 
6
7
  export interface ProviderQuotaIdentityInput {
7
8
  appId: string;
@@ -398,7 +399,7 @@ async function ensureCooldownTable(): Promise<void> {
398
399
  state.initPromise = (async () => {
399
400
  const db = getDbExec();
400
401
  const integerType = intType();
401
- await db.execute(`
402
+ const createSql = `
402
403
  CREATE TABLE IF NOT EXISTS provider_api_cooldowns (
403
404
  quota_key TEXT NOT NULL,
404
405
  provider_id TEXT NOT NULL,
@@ -409,7 +410,18 @@ async function ensureCooldownTable(): Promise<void> {
409
410
  updated_at ${integerType} NOT NULL,
410
411
  PRIMARY KEY (quota_key)
411
412
  )
412
- `);
413
+ `;
414
+ if (isPostgres()) {
415
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
416
+ await ensureTableExists("provider_api_cooldowns", createSql);
417
+ await ensureIndexExists(
418
+ "provider_api_cooldowns_provider_idx",
419
+ `CREATE INDEX IF NOT EXISTS provider_api_cooldowns_provider_idx ON provider_api_cooldowns (provider_id, cooldown_until)`,
420
+ );
421
+ return;
422
+ }
423
+ // SQLite (local dev): keep existing behavior
424
+ await db.execute(createSql);
413
425
  try {
414
426
  await db.execute(
415
427
  `CREATE INDEX IF NOT EXISTS provider_api_cooldowns_provider_idx ON provider_api_cooldowns (provider_id, cooldown_until)`,
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import { getDbExec, intType, isPostgres, type DbExec } from "../db/client.js";
17
+ import { ensureTableExists, ensureIndexExists } from "../db/ddl-guard.js";
17
18
 
18
19
  // ---------------------------------------------------------------------------
19
20
  // Caps
@@ -32,7 +33,7 @@ async function ensureTables(): Promise<void> {
32
33
  _initPromise = (async () => {
33
34
  const db = getDbExec();
34
35
  const integerType = intType();
35
- await db.execute(`
36
+ const createDatasetsSql = `
36
37
  CREATE TABLE IF NOT EXISTS staged_datasets (
37
38
  id TEXT NOT NULL,
38
39
  app_id TEXT NOT NULL,
@@ -45,18 +46,34 @@ async function ensureTables(): Promise<void> {
45
46
  updated_at ${integerType} NOT NULL,
46
47
  PRIMARY KEY (id)
47
48
  )
48
- `);
49
- await db.execute(`
49
+ `;
50
+ const createRowsSql = `
50
51
  CREATE TABLE IF NOT EXISTS staged_dataset_rows (
51
52
  dataset_id TEXT NOT NULL,
52
53
  row_index ${integerType} NOT NULL,
53
54
  row_data TEXT NOT NULL,
54
55
  PRIMARY KEY (dataset_id, row_index)
55
56
  )
56
- `);
57
+ `;
57
58
  if (isPostgres()) {
58
- await widenPostgresIntegerColumns(db);
59
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
60
+ await ensureTableExists("staged_datasets", createDatasetsSql);
61
+ await ensureTableExists("staged_dataset_rows", createRowsSql);
62
+ await widenPostgresIntegerColumns(db); // best-effort type widening — unchanged
63
+ await ensureIndexExists(
64
+ "staged_datasets_scope_idx",
65
+ `CREATE INDEX IF NOT EXISTS staged_datasets_scope_idx ON staged_datasets (app_id, owner_email)`,
66
+ );
67
+ await ensureIndexExists(
68
+ "staged_dataset_rows_dataset_idx",
69
+ `CREATE INDEX IF NOT EXISTS staged_dataset_rows_dataset_idx ON staged_dataset_rows (dataset_id)`,
70
+ );
71
+ return;
59
72
  }
73
+ // SQLite (local dev): keep existing behavior
74
+ await db.execute(createDatasetsSql);
75
+ await db.execute(createRowsSql);
76
+ // Note: widenPostgresIntegerColumns is Postgres-only, skip on SQLite
60
77
  for (const ddl of [
61
78
  `CREATE INDEX IF NOT EXISTS staged_datasets_scope_idx ON staged_datasets (app_id, owner_email)`,
62
79
  `CREATE INDEX IF NOT EXISTS staged_dataset_rows_dataset_idx ON staged_dataset_rows (dataset_id)`,
@@ -7,6 +7,7 @@ import {
7
7
  retryOnDdlRace,
8
8
  type DbExec,
9
9
  } from "../db/client.js";
10
+ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
10
11
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
11
12
  import {
12
13
  canUseLocalWorkspaceResourcePath,
@@ -751,37 +752,71 @@ async function ensureTable(): Promise<void> {
751
752
 
752
753
  async function _doEnsureTable(): Promise<void> {
753
754
  const client = getDbExec();
754
- await retryOnDdlRace(() =>
755
- client.execute(`
756
- CREATE TABLE IF NOT EXISTS resources (
757
- id TEXT PRIMARY KEY,
758
- path TEXT NOT NULL,
759
- owner TEXT NOT NULL,
760
- content TEXT NOT NULL DEFAULT '',
761
- mime_type TEXT NOT NULL DEFAULT 'text/markdown',
762
- size ${intType()} NOT NULL DEFAULT 0,
763
- created_at ${intType()} NOT NULL,
764
- updated_at ${intType()} NOT NULL,
765
- created_by TEXT NOT NULL DEFAULT 'user',
766
- visibility TEXT NOT NULL DEFAULT 'workspace',
767
- thread_id TEXT,
768
- run_id TEXT,
769
- expires_at ${intType()},
770
- metadata TEXT,
771
- UNIQUE(path, owner)
772
- )
773
- `),
774
- );
775
-
776
- await ensureResourceColumn(client, "created_by TEXT NOT NULL DEFAULT 'user'");
777
- await ensureResourceColumn(
778
- client,
779
- "visibility TEXT NOT NULL DEFAULT 'workspace'",
780
- );
781
- await ensureResourceColumn(client, "thread_id TEXT");
782
- await ensureResourceColumn(client, "run_id TEXT");
783
- await ensureResourceColumn(client, `expires_at ${intType()}`);
784
- await ensureResourceColumn(client, "metadata TEXT");
755
+ const createSql = `
756
+ CREATE TABLE IF NOT EXISTS resources (
757
+ id TEXT PRIMARY KEY,
758
+ path TEXT NOT NULL,
759
+ owner TEXT NOT NULL,
760
+ content TEXT NOT NULL DEFAULT '',
761
+ mime_type TEXT NOT NULL DEFAULT 'text/markdown',
762
+ size ${intType()} NOT NULL DEFAULT 0,
763
+ created_at ${intType()} NOT NULL,
764
+ updated_at ${intType()} NOT NULL,
765
+ created_by TEXT NOT NULL DEFAULT 'user',
766
+ visibility TEXT NOT NULL DEFAULT 'workspace',
767
+ thread_id TEXT,
768
+ run_id TEXT,
769
+ expires_at ${intType()},
770
+ metadata TEXT,
771
+ UNIQUE(path, owner)
772
+ )
773
+ `;
774
+
775
+ if (isPostgres()) {
776
+ // Hot path: the `resources` table and its additive columns are virtually
777
+ // always already present in production. Issuing `CREATE TABLE`/`ALTER
778
+ // TABLE` still takes an ACCESS EXCLUSIVE lock that, in a fresh
779
+ // background-worker process behind a concurrent connection on the shared
780
+ // Neon DB, can block ~indefinitely. The ensure* wrappers probe
781
+ // `information_schema` first (plain reads, no lock) and run DDL ONLY for
782
+ // what is actually missing, bounded by a transaction-scoped `lock_timeout`.
783
+ // If a swallowed lock-timeout leaves the schema still missing they RE-PROBE
784
+ // and THROW rather than letting init memoize success against absent schema.
785
+ await ensureTableExists("resources", createSql);
786
+ // Additive columns — guarded so the hot path (columns already present)
787
+ // skips the ACCESS EXCLUSIVE ALTER entirely.
788
+ const pgColumns: Array<[string, string]> = [
789
+ ["created_by", "TEXT NOT NULL DEFAULT 'user'"],
790
+ ["visibility", "TEXT NOT NULL DEFAULT 'workspace'"],
791
+ ["thread_id", "TEXT"],
792
+ ["run_id", "TEXT"],
793
+ ["expires_at", intType()],
794
+ ["metadata", "TEXT"],
795
+ ];
796
+ for (const [col, def] of pgColumns) {
797
+ await ensureColumnExists(
798
+ "resources",
799
+ col,
800
+ `ALTER TABLE resources ADD COLUMN IF NOT EXISTS ${col} ${def}`,
801
+ );
802
+ }
803
+ } else {
804
+ // SQLite (local dev): no lock problem — keep the original behaviour using
805
+ // `retryOnDdlRace` + `ensureResourceColumn` (duplicate-column catch).
806
+ await retryOnDdlRace(() => client.execute(createSql));
807
+ await ensureResourceColumn(
808
+ client,
809
+ "created_by TEXT NOT NULL DEFAULT 'user'",
810
+ );
811
+ await ensureResourceColumn(
812
+ client,
813
+ "visibility TEXT NOT NULL DEFAULT 'workspace'",
814
+ );
815
+ await ensureResourceColumn(client, "thread_id TEXT");
816
+ await ensureResourceColumn(client, "run_id TEXT");
817
+ await ensureResourceColumn(client, `expires_at ${intType()}`);
818
+ await ensureResourceColumn(client, "metadata TEXT");
819
+ }
785
820
 
786
821
  // Older deployments have 32-bit timestamp columns; on Postgres the
787
822
  // `Date.now()` written on insert/update overflows int4. Widen in place
@@ -15,11 +15,7 @@
15
15
  import { randomUUID } from "node:crypto";
16
16
 
17
17
  import { getDbExec, isPostgres } from "../db/client.js";
18
- import {
19
- pgColumnExists,
20
- pgTableExists,
21
- runGuardedDdl,
22
- } from "../db/ddl-guard.js";
18
+ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
23
19
  import {
24
20
  encryptSecretValue as encryptValue,
25
21
  decryptSecretValue as decryptValue,
@@ -48,23 +44,23 @@ async function ensureTable(): Promise<void> {
48
44
  // virtually always already present. Issuing `CREATE`/`ALTER` would
49
45
  // still take an ACCESS EXCLUSIVE lock — which, in a fresh background
50
46
  // worker process behind a concurrent connection on the shared Neon DB,
51
- // can block ~indefinitely. So check `information_schema` first (a plain
52
- // read, no lock) and run DDL ONLY for what is actually missing. When
53
- // DDL must run, `runGuardedDdl` wraps it in a transaction-scoped
54
- // `lock_timeout` so a contended lock fails fast instead of hanging.
55
- if (!(await pgTableExists("app_secrets"))) {
56
- await runGuardedDdl(createSql);
57
- }
58
- if (!(await pgColumnExists("app_secrets", "description"))) {
59
- await runGuardedDdl(
60
- `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS description TEXT`,
61
- );
62
- }
63
- if (!(await pgColumnExists("app_secrets", "url_allowlist"))) {
64
- await runGuardedDdl(
65
- `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS url_allowlist TEXT`,
66
- );
67
- }
47
+ // can block ~indefinitely. `ensureTableExists` / `ensureColumnExists`
48
+ // check `information_schema` first (a plain read, no lock) and run DDL
49
+ // ONLY for what is actually missing, wrapping any DDL that must run in a
50
+ // transaction-scoped `lock_timeout` so a contended lock fails fast. They
51
+ // also re-probe after a swallowed lock-timeout and THROW if the schema
52
+ // is still missing, so a timed-out DDL never poisons this init memo.
53
+ await ensureTableExists("app_secrets", createSql);
54
+ await ensureColumnExists(
55
+ "app_secrets",
56
+ "description",
57
+ `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS description TEXT`,
58
+ );
59
+ await ensureColumnExists(
60
+ "app_secrets",
61
+ "url_allowlist",
62
+ `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS url_allowlist TEXT`,
63
+ );
68
64
  return;
69
65
  }
70
66
 
@@ -14,7 +14,13 @@
14
14
  * for UI/status; this table only drives dispatch, idempotent claiming, and
15
15
  * cross-invocation continuation.
16
16
  */
17
- import { getDbExec, intType, retryOnDdlRace } from "../db/client.js";
17
+ import {
18
+ getDbExec,
19
+ intType,
20
+ isPostgres,
21
+ retryOnDdlRace,
22
+ } from "../db/client.js";
23
+ import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
18
24
 
19
25
  /** Max cross-invocation continuations for one sub-agent run. Each continuation
20
26
  * is one ~40s soft-timeout chunk, so ~60 ≈ ~40 minutes of wall-clock work. Two
@@ -77,8 +83,7 @@ async function ensureTable(): Promise<void> {
77
83
  if (!_initPromise) {
78
84
  _initPromise = (async () => {
79
85
  const client = getDbExec();
80
- await retryOnDdlRace(() =>
81
- client.execute(`
86
+ const createSql = `
82
87
  CREATE TABLE IF NOT EXISTS agent_team_run_queue (
83
88
  task_id TEXT PRIMARY KEY,
84
89
  thread_id TEXT NOT NULL,
@@ -92,13 +97,20 @@ async function ensureTable(): Promise<void> {
92
97
  created_at ${intType()} NOT NULL,
93
98
  updated_at ${intType()} NOT NULL
94
99
  )
95
- `),
96
- );
97
- await retryOnDdlRace(() =>
98
- client.execute(
99
- `CREATE INDEX IF NOT EXISTS idx_agent_team_run_queue_status ON agent_team_run_queue (status, updated_at)`,
100
- ),
101
- );
100
+ `;
101
+ const indexSql = `CREATE INDEX IF NOT EXISTS idx_agent_team_run_queue_status ON agent_team_run_queue (status, updated_at)`;
102
+
103
+ // PG guard: probe information_schema / pg_indexes first (no lock), run
104
+ // DDL only when missing, bounded by a transaction-scoped lock_timeout.
105
+ if (isPostgres()) {
106
+ await ensureTableExists("agent_team_run_queue", createSql);
107
+ await ensureIndexExists("idx_agent_team_run_queue_status", indexSql);
108
+ return;
109
+ }
110
+
111
+ // SQLite (local dev): no lock problem — keep the original behaviour.
112
+ await retryOnDdlRace(() => client.execute(createSql));
113
+ await retryOnDdlRace(() => client.execute(indexSql));
102
114
  })().catch((err) => {
103
115
  _initPromise = undefined;
104
116
  throw err;
@@ -72,6 +72,7 @@ import {
72
72
  retryOnDdlRace,
73
73
  describeDbError,
74
74
  } from "../db/client.js";
75
+ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
75
76
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
76
77
  import { readBody } from "../server/h3-helpers.js";
77
78
  import { putSetting } from "../settings/store.js";
@@ -865,15 +866,32 @@ async function ensureSessionTable(): Promise<void> {
865
866
  if (!_sessionInitPromise) {
866
867
  _sessionInitPromise = (async () => {
867
868
  const client = getDbExec();
868
- await retryOnDdlRace(() =>
869
- client.execute(`
869
+ const createSql = `
870
870
  CREATE TABLE IF NOT EXISTS sessions (
871
871
  token TEXT PRIMARY KEY,
872
872
  email TEXT,
873
873
  created_at ${intType()} NOT NULL
874
874
  )
875
- `),
876
- );
875
+ `;
876
+
877
+ // PG guard: probe information_schema first (no lock), run DDL only when
878
+ // missing, bounded by a transaction-scoped lock_timeout.
879
+ if (isPostgres()) {
880
+ await ensureTableExists("sessions", createSql);
881
+ await ensureColumnExists(
882
+ "sessions",
883
+ "email",
884
+ `ALTER TABLE sessions ADD COLUMN IF NOT EXISTS email TEXT`,
885
+ );
886
+ // Older deployments have a 32-bit `created_at`; on Postgres the
887
+ // `Date.now()` written on session create overflows int4. Widen in place
888
+ // (no-op once done / on fresh DBs).
889
+ await widenIntColumnsToBigInt("sessions", ["created_at"]);
890
+ return;
891
+ }
892
+
893
+ // SQLite (local dev): no lock problem — keep the original behaviour.
894
+ await retryOnDdlRace(() => client.execute(createSql));
877
895
  try {
878
896
  await client.execute(`ALTER TABLE sessions ADD COLUMN email TEXT`);
879
897
  } catch {
@@ -39,6 +39,7 @@ import {
39
39
  neonPoolMax,
40
40
  attachNeonPoolErrorLogger,
41
41
  } from "../db/client.js";
42
+ import { ensureTableExists } from "../db/ddl-guard.js";
42
43
  import { saveOAuthTokens } from "../oauth-tokens/store.js";
43
44
  import { acceptPendingInvitationsForEmail } from "../org/accept-pending.js";
44
45
  import { autoJoinDomainMatchingOrgs } from "../org/auto-join-domain.js";
@@ -693,29 +694,62 @@ async function mirrorGoogleAccountToOAuthTokens(account: {
693
694
 
694
695
  async function ensureBetterAuthTables(): Promise<void> {
695
696
  const db = getDbExec();
696
- const statements = isPostgres()
697
- ? [
697
+
698
+ // PG guard: probe information_schema first (no lock) for each table; run
699
+ // DDL only when missing, bounded by a transaction-scoped lock_timeout.
700
+ // Probe names are UNQUOTED (what information_schema.tables.table_name stores);
701
+ // createSql keeps the QUOTED "user"/"session"/… form required by Postgres.
702
+ if (isPostgres()) {
703
+ const pgTables: Array<[name: string, createSql: string]> = [
704
+ [
705
+ "user",
698
706
  `CREATE TABLE IF NOT EXISTS "user" (id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, email_verified BOOLEAN NOT NULL DEFAULT FALSE, image TEXT, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
707
+ ],
708
+ [
709
+ "session",
699
710
  `CREATE TABLE IF NOT EXISTS "session" (id TEXT PRIMARY KEY, expires_at TIMESTAMPTZ NOT NULL, token TEXT NOT NULL UNIQUE, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, ip_address TEXT, user_agent TEXT, user_id TEXT NOT NULL, active_organization_id TEXT)`,
711
+ ],
712
+ [
713
+ "account",
700
714
  `CREATE TABLE IF NOT EXISTS "account" (id TEXT PRIMARY KEY, account_id TEXT NOT NULL, provider_id TEXT NOT NULL, user_id TEXT NOT NULL, access_token TEXT, refresh_token TEXT, id_token TEXT, access_token_expires_at TIMESTAMPTZ, refresh_token_expires_at TIMESTAMPTZ, scope TEXT, password TEXT, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
715
+ ],
716
+ [
717
+ "verification",
701
718
  `CREATE TABLE IF NOT EXISTS "verification" (id TEXT PRIMARY KEY, identifier TEXT NOT NULL, value TEXT NOT NULL, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
719
+ ],
720
+ [
721
+ "organization",
702
722
  `CREATE TABLE IF NOT EXISTS "organization" (id TEXT PRIMARY KEY, name TEXT NOT NULL, slug TEXT NOT NULL UNIQUE, logo TEXT, metadata TEXT, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
723
+ ],
724
+ [
725
+ "member",
703
726
  `CREATE TABLE IF NOT EXISTS "member" (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, user_id TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'member', created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
727
+ ],
728
+ [
729
+ "invitation",
704
730
  `CREATE TABLE IF NOT EXISTS "invitation" (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, email TEXT NOT NULL, role TEXT, status TEXT NOT NULL DEFAULT 'pending', expires_at TIMESTAMPTZ NOT NULL, inviter_id TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
731
+ ],
732
+ [
733
+ "jwks",
705
734
  `CREATE TABLE IF NOT EXISTS "jwks" (id TEXT PRIMARY KEY, public_key TEXT NOT NULL, private_key TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL, expires_at TIMESTAMPTZ)`,
706
- ]
707
- : [
708
- `CREATE TABLE IF NOT EXISTS user (id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, email_verified INTEGER NOT NULL DEFAULT 0, image TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
709
- `CREATE TABLE IF NOT EXISTS session (id TEXT PRIMARY KEY, expires_at INTEGER NOT NULL, token TEXT NOT NULL UNIQUE, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, ip_address TEXT, user_agent TEXT, user_id TEXT NOT NULL, active_organization_id TEXT)`,
710
- `CREATE TABLE IF NOT EXISTS account (id TEXT PRIMARY KEY, account_id TEXT NOT NULL, provider_id TEXT NOT NULL, user_id TEXT NOT NULL, access_token TEXT, refresh_token TEXT, id_token TEXT, access_token_expires_at INTEGER, refresh_token_expires_at INTEGER, scope TEXT, password TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
711
- `CREATE TABLE IF NOT EXISTS verification (id TEXT PRIMARY KEY, identifier TEXT NOT NULL, value TEXT NOT NULL, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
712
- `CREATE TABLE IF NOT EXISTS organization (id TEXT PRIMARY KEY, name TEXT NOT NULL, slug TEXT NOT NULL UNIQUE, logo TEXT, metadata TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
713
- `CREATE TABLE IF NOT EXISTS member (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, user_id TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'member', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
714
- `CREATE TABLE IF NOT EXISTS invitation (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, email TEXT NOT NULL, role TEXT, status TEXT NOT NULL DEFAULT 'pending', expires_at INTEGER NOT NULL, inviter_id TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
715
- `CREATE TABLE IF NOT EXISTS jwks (id TEXT PRIMARY KEY, public_key TEXT NOT NULL, private_key TEXT NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER)`,
716
- ];
717
-
718
- for (const sql of statements) await db.execute(sql);
735
+ ],
736
+ ];
737
+ for (const [name, sql] of pgTables) await ensureTableExists(name, sql);
738
+ return;
739
+ }
740
+
741
+ // SQLite (local dev): no lock problem keep the original behaviour.
742
+ const sqliteStatements = [
743
+ `CREATE TABLE IF NOT EXISTS user (id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, email_verified INTEGER NOT NULL DEFAULT 0, image TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
744
+ `CREATE TABLE IF NOT EXISTS session (id TEXT PRIMARY KEY, expires_at INTEGER NOT NULL, token TEXT NOT NULL UNIQUE, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, ip_address TEXT, user_agent TEXT, user_id TEXT NOT NULL, active_organization_id TEXT)`,
745
+ `CREATE TABLE IF NOT EXISTS account (id TEXT PRIMARY KEY, account_id TEXT NOT NULL, provider_id TEXT NOT NULL, user_id TEXT NOT NULL, access_token TEXT, refresh_token TEXT, id_token TEXT, access_token_expires_at INTEGER, refresh_token_expires_at INTEGER, scope TEXT, password TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
746
+ `CREATE TABLE IF NOT EXISTS verification (id TEXT PRIMARY KEY, identifier TEXT NOT NULL, value TEXT NOT NULL, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
747
+ `CREATE TABLE IF NOT EXISTS organization (id TEXT PRIMARY KEY, name TEXT NOT NULL, slug TEXT NOT NULL UNIQUE, logo TEXT, metadata TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
748
+ `CREATE TABLE IF NOT EXISTS member (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, user_id TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'member', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
749
+ `CREATE TABLE IF NOT EXISTS invitation (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, email TEXT NOT NULL, role TEXT, status TEXT NOT NULL DEFAULT 'pending', expires_at INTEGER NOT NULL, inviter_id TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
750
+ `CREATE TABLE IF NOT EXISTS jwks (id TEXT PRIMARY KEY, public_key TEXT NOT NULL, private_key TEXT NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER)`,
751
+ ];
752
+ for (const sql of sqliteStatements) await db.execute(sql);
719
753
  }
720
754
 
721
755
  /**
@@ -9,7 +9,8 @@ import {
9
9
  setResponseHeader,
10
10
  } from "h3";
11
11
 
12
- import { getDbExec, intType } from "../db/client.js";
12
+ import { getDbExec, intType, isPostgres } from "../db/client.js";
13
+ import { ensureTableExists } from "../db/ddl-guard.js";
13
14
  import {
14
15
  EMBED_MODE_QUERY_PARAM,
15
16
  EMBED_SESSION_COOKIE,
@@ -103,8 +104,10 @@ export type ResolvedEmbedSession = {
103
104
  async function ensureTable(): Promise<void> {
104
105
  if (!_initPromise) {
105
106
  _initPromise = (async () => {
106
- const client = getDbExec();
107
- await client.execute(`
107
+ // Build the CREATE SQL here (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
+ const embedTicketsCreateSql = `
108
111
  CREATE TABLE IF NOT EXISTS agent_native_embed_tickets (
109
112
  ticket_hash TEXT PRIMARY KEY,
110
113
  owner_email TEXT NOT NULL,
@@ -115,7 +118,19 @@ async function ensureTable(): Promise<void> {
115
118
  expires_at ${intType()} NOT NULL,
116
119
  consumed_at ${intType()}
117
120
  )
118
- `);
121
+ `;
122
+ if (isPostgres()) {
123
+ // PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
124
+ await ensureTableExists(
125
+ "agent_native_embed_tickets",
126
+ embedTicketsCreateSql,
127
+ );
128
+ return;
129
+ }
130
+
131
+ // SQLite (local dev): no lock problem — keep the original behaviour.
132
+ const client = getDbExec();
133
+ await client.execute(embedTicketsCreateSql);
119
134
  })().catch((err) => {
120
135
  _initPromise = undefined;
121
136
  throw err;