@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
@@ -4,6 +4,11 @@ import {
4
4
  intType,
5
5
  retryOnDdlRace,
6
6
  } from "../db/client.js";
7
+ import {
8
+ ensureTableExists,
9
+ ensureColumnExists,
10
+ ensureIndexExists,
11
+ } from "../db/ddl-guard.js";
7
12
  import { isDuplicateColumnError } from "../db/migrations.js";
8
13
  import type { IncomingMessage } from "./types.js";
9
14
 
@@ -11,36 +16,74 @@ let _initPromise: Promise<void> | undefined;
11
16
  const PROCESSING_STUCK_AFTER_MS = 5 * 60 * 1000;
12
17
  const PROCESSING_NEXT_CHECK_STALE_AFTER_MS = 60 * 1000;
13
18
 
19
+ // Build the CREATE SQL lazily (not at module scope) so intType() runs at
20
+ // RUNTIME, not import time — a module-scope call breaks any consumer whose
21
+ // db/client mock doesn't stub intType (e.g. db-admin specs).
22
+ function buildCreateSql(): string {
23
+ return `
24
+ CREATE TABLE IF NOT EXISTS integration_a2a_continuations (
25
+ id TEXT PRIMARY KEY,
26
+ integration_task_id TEXT NOT NULL,
27
+ platform TEXT NOT NULL,
28
+ external_thread_id TEXT NOT NULL,
29
+ incoming_payload TEXT NOT NULL,
30
+ placeholder_ref TEXT,
31
+ owner_email TEXT NOT NULL,
32
+ org_id TEXT,
33
+ agent_name TEXT NOT NULL,
34
+ agent_url TEXT NOT NULL,
35
+ dedupe_key TEXT,
36
+ a2a_task_id TEXT NOT NULL,
37
+ a2a_auth_token TEXT,
38
+ status TEXT NOT NULL,
39
+ attempts ${intType()} NOT NULL DEFAULT 0,
40
+ next_check_at ${intType()} NOT NULL,
41
+ error_message TEXT,
42
+ created_at ${intType()} NOT NULL,
43
+ updated_at ${intType()} NOT NULL,
44
+ completed_at ${intType()}
45
+ )
46
+ `;
47
+ }
48
+
14
49
  async function ensureTable(): Promise<void> {
15
50
  if (!_initPromise) {
16
51
  _initPromise = (async () => {
17
52
  const client = getDbExec();
18
- await retryOnDdlRace(() =>
19
- client.execute(`
20
- CREATE TABLE IF NOT EXISTS integration_a2a_continuations (
21
- id TEXT PRIMARY KEY,
22
- integration_task_id TEXT NOT NULL,
23
- platform TEXT NOT NULL,
24
- external_thread_id TEXT NOT NULL,
25
- incoming_payload TEXT NOT NULL,
26
- placeholder_ref TEXT,
27
- owner_email TEXT NOT NULL,
28
- org_id TEXT,
29
- agent_name TEXT NOT NULL,
30
- agent_url TEXT NOT NULL,
31
- dedupe_key TEXT,
32
- a2a_task_id TEXT NOT NULL,
33
- a2a_auth_token TEXT,
34
- status TEXT NOT NULL,
35
- attempts ${intType()} NOT NULL DEFAULT 0,
36
- next_check_at ${intType()} NOT NULL,
37
- error_message TEXT,
38
- created_at ${intType()} NOT NULL,
39
- updated_at ${intType()} NOT NULL,
40
- completed_at ${intType()}
41
- )
42
- `),
43
- );
53
+ const createSql = buildCreateSql();
54
+ if (isPostgres()) {
55
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
56
+ await ensureTableExists("integration_a2a_continuations", createSql);
57
+ await ensureIndexExists(
58
+ "idx_a2a_continuations_status_next",
59
+ `CREATE INDEX IF NOT EXISTS idx_a2a_continuations_status_next ON integration_a2a_continuations(status, next_check_at)`,
60
+ );
61
+ await ensureIndexExists(
62
+ "idx_a2a_continuations_integration_task",
63
+ `CREATE INDEX IF NOT EXISTS idx_a2a_continuations_integration_task ON integration_a2a_continuations(integration_task_id)`,
64
+ );
65
+ await ensureIndexExists(
66
+ "idx_a2a_continuations_remote_task",
67
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_a2a_continuations_remote_task ON integration_a2a_continuations(integration_task_id, agent_url, a2a_task_id)`,
68
+ );
69
+ await ensureColumnExists(
70
+ "integration_a2a_continuations",
71
+ "a2a_auth_token",
72
+ `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS a2a_auth_token TEXT`,
73
+ );
74
+ await ensureColumnExists(
75
+ "integration_a2a_continuations",
76
+ "dedupe_key",
77
+ `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS dedupe_key TEXT`,
78
+ );
79
+ await ensureIndexExists(
80
+ "idx_a2a_continuations_dedupe_key",
81
+ `CREATE INDEX IF NOT EXISTS idx_a2a_continuations_dedupe_key ON integration_a2a_continuations(integration_task_id, agent_url, dedupe_key)`,
82
+ );
83
+ return;
84
+ }
85
+ // SQLite (local dev): keep existing behavior
86
+ await retryOnDdlRace(() => client.execute(createSql));
44
87
  await retryOnDdlRace(() =>
45
88
  client.execute(
46
89
  `CREATE INDEX IF NOT EXISTS idx_a2a_continuations_status_next ON integration_a2a_continuations(status, next_check_at)`,
@@ -1,9 +1,5 @@
1
- import {
2
- getDbExec,
3
- isPostgres,
4
- intType,
5
- retryOnDdlRace,
6
- } from "../db/client.js";
1
+ import { getDbExec, isPostgres, intType } from "../db/client.js";
2
+ import { ensureTableExists } from "../db/ddl-guard.js";
7
3
 
8
4
  let _initPromise: Promise<void> | undefined;
9
5
 
@@ -11,18 +7,23 @@ async function ensureTable(): Promise<void> {
11
7
  if (!_initPromise) {
12
8
  _initPromise = (async () => {
13
9
  const client = getDbExec();
14
- await retryOnDdlRace(() =>
15
- client.execute(`
16
- CREATE TABLE IF NOT EXISTS integration_configs (
17
- platform TEXT NOT NULL,
18
- config_key TEXT NOT NULL,
19
- config_data TEXT NOT NULL,
20
- owner TEXT,
21
- updated_at ${intType()} NOT NULL,
22
- PRIMARY KEY (platform, config_key)
23
- )
24
- `),
25
- );
10
+ const createSql = `CREATE TABLE IF NOT EXISTS integration_configs (
11
+ platform TEXT NOT NULL,
12
+ config_key TEXT NOT NULL,
13
+ config_data TEXT NOT NULL,
14
+ owner TEXT,
15
+ updated_at ${intType()} NOT NULL,
16
+ PRIMARY KEY (platform, config_key)
17
+ )`;
18
+
19
+ if (isPostgres()) {
20
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
21
+ await ensureTableExists("integration_configs", createSql);
22
+ return;
23
+ }
24
+
25
+ // SQLite (local dev): keep existing behavior
26
+ await client.execute(createSql);
26
27
  })().catch((err) => {
27
28
  // Don't cache the rejection — let the next caller retry a fresh init.
28
29
  _initPromise = undefined;
@@ -11,6 +11,11 @@
11
11
  * invocation gets its own fresh function timeout budget.
12
12
  */
13
13
  import { getDbExec, isPostgres, intType } from "../db/client.js";
14
+ import {
15
+ ensureTableExists,
16
+ ensureColumnExists,
17
+ ensureIndexExists,
18
+ } from "../db/ddl-guard.js";
14
19
 
15
20
  let _initPromise: Promise<void> | undefined;
16
21
 
@@ -18,22 +23,41 @@ async function ensureTable(): Promise<void> {
18
23
  if (!_initPromise) {
19
24
  _initPromise = (async () => {
20
25
  const client = getDbExec();
21
- await client.execute(`
22
- CREATE TABLE IF NOT EXISTS integration_pending_tasks (
23
- id TEXT PRIMARY KEY,
24
- platform TEXT NOT NULL,
25
- external_thread_id TEXT NOT NULL,
26
- payload TEXT NOT NULL,
27
- owner_email TEXT NOT NULL,
28
- org_id TEXT,
29
- status TEXT NOT NULL,
30
- attempts ${intType()} NOT NULL DEFAULT 0,
31
- error_message TEXT,
32
- created_at ${intType()} NOT NULL,
33
- updated_at ${intType()} NOT NULL,
34
- completed_at ${intType()}
35
- )
36
- `);
26
+ const createSql = `CREATE TABLE IF NOT EXISTS integration_pending_tasks (
27
+ id TEXT PRIMARY KEY,
28
+ platform TEXT NOT NULL,
29
+ external_thread_id TEXT NOT NULL,
30
+ payload TEXT NOT NULL,
31
+ owner_email TEXT NOT NULL,
32
+ org_id TEXT,
33
+ status TEXT NOT NULL,
34
+ attempts ${intType()} NOT NULL DEFAULT 0,
35
+ error_message TEXT,
36
+ created_at ${intType()} NOT NULL,
37
+ updated_at ${intType()} NOT NULL,
38
+ completed_at ${intType()}
39
+ )`;
40
+
41
+ if (isPostgres()) {
42
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
43
+ await ensureTableExists("integration_pending_tasks", createSql);
44
+ await ensureColumnExists(
45
+ "integration_pending_tasks",
46
+ "external_event_key",
47
+ `ALTER TABLE integration_pending_tasks ADD COLUMN IF NOT EXISTS external_event_key TEXT`,
48
+ );
49
+ await ensureIndexExists(
50
+ "idx_pending_tasks_status_created",
51
+ `CREATE INDEX IF NOT EXISTS idx_pending_tasks_status_created ON integration_pending_tasks(status, created_at)`,
52
+ );
53
+ await ensureIndexExists(
54
+ "idx_pending_tasks_event_key",
55
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_pending_tasks_event_key ON integration_pending_tasks(platform, external_event_key)`,
56
+ );
57
+ return;
58
+ }
59
+
60
+ await client.execute(createSql);
37
61
  await client.execute(
38
62
  `CREATE INDEX IF NOT EXISTS idx_pending_tasks_status_created ON integration_pending_tasks(status, created_at)`,
39
63
  );
@@ -4,6 +4,7 @@ import {
4
4
  isPostgres,
5
5
  retryOnDdlRace,
6
6
  } from "../db/client.js";
7
+ import { ensureTableExists, ensureIndexExists } from "../db/ddl-guard.js";
7
8
  import type {
8
9
  RemoteCommand,
9
10
  RemoteCommandKind,
@@ -29,29 +30,42 @@ async function ensureTable(): Promise<void> {
29
30
  if (!_initPromise) {
30
31
  _initPromise = (async () => {
31
32
  const client = getDbExec();
32
- await retryOnDdlRace(() =>
33
- client.execute(`
34
- CREATE TABLE IF NOT EXISTS integration_remote_commands (
35
- id TEXT PRIMARY KEY,
36
- device_id TEXT NOT NULL,
37
- owner_email TEXT NOT NULL,
38
- org_id TEXT,
39
- kind TEXT NOT NULL,
40
- params_json TEXT NOT NULL,
41
- status TEXT NOT NULL,
42
- result_json TEXT,
43
- platform TEXT,
44
- external_thread_id TEXT,
45
- attempts ${intType()} NOT NULL DEFAULT 0,
46
- next_check_at ${intType()} NOT NULL,
47
- claimed_at ${intType()},
48
- completed_at ${intType()},
49
- error_message TEXT,
50
- created_at ${intType()} NOT NULL,
51
- updated_at ${intType()} NOT NULL
52
- )
53
- `),
54
- );
33
+ const createSql = `CREATE TABLE IF NOT EXISTS integration_remote_commands (
34
+ id TEXT PRIMARY KEY,
35
+ device_id TEXT NOT NULL,
36
+ owner_email TEXT NOT NULL,
37
+ org_id TEXT,
38
+ kind TEXT NOT NULL,
39
+ params_json TEXT NOT NULL,
40
+ status TEXT NOT NULL,
41
+ result_json TEXT,
42
+ platform TEXT,
43
+ external_thread_id TEXT,
44
+ attempts ${intType()} NOT NULL DEFAULT 0,
45
+ next_check_at ${intType()} NOT NULL,
46
+ claimed_at ${intType()},
47
+ completed_at ${intType()},
48
+ error_message TEXT,
49
+ created_at ${intType()} NOT NULL,
50
+ updated_at ${intType()} NOT NULL
51
+ )`;
52
+
53
+ if (isPostgres()) {
54
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
55
+ await ensureTableExists("integration_remote_commands", createSql);
56
+ await ensureIndexExists(
57
+ "idx_remote_commands_device_status_next",
58
+ `CREATE INDEX IF NOT EXISTS idx_remote_commands_device_status_next ON integration_remote_commands(device_id, status, next_check_at)`,
59
+ );
60
+ await ensureIndexExists(
61
+ "idx_remote_commands_owner",
62
+ `CREATE INDEX IF NOT EXISTS idx_remote_commands_owner ON integration_remote_commands(owner_email, org_id)`,
63
+ );
64
+ return;
65
+ }
66
+
67
+ // SQLite: keep existing behavior
68
+ await retryOnDdlRace(() => client.execute(createSql));
55
69
  await retryOnDdlRace(() =>
56
70
  client.execute(
57
71
  `CREATE INDEX IF NOT EXISTS idx_remote_commands_device_status_next ON integration_remote_commands(device_id, status, next_check_at)`,
@@ -4,6 +4,11 @@ import {
4
4
  isPostgres,
5
5
  retryOnDdlRace,
6
6
  } from "../db/client.js";
7
+ import {
8
+ ensureColumnExists,
9
+ ensureIndexExists,
10
+ ensureTableExists,
11
+ } from "../db/ddl-guard.js";
7
12
  import type { PublicRemoteDevice, RemoteDevice } from "./remote-types.js";
8
13
 
9
14
  let _initPromise: Promise<void> | undefined;
@@ -12,26 +17,65 @@ async function ensureTable(): Promise<void> {
12
17
  if (!_initPromise) {
13
18
  _initPromise = (async () => {
14
19
  const client = getDbExec();
15
- await retryOnDdlRace(() =>
16
- client.execute(`
17
- CREATE TABLE IF NOT EXISTS integration_remote_devices (
18
- id TEXT PRIMARY KEY,
19
- owner_email TEXT NOT NULL,
20
- org_id TEXT,
21
- label TEXT NOT NULL,
22
- platform TEXT,
23
- app_version TEXT,
24
- host_name TEXT,
25
- metadata_json TEXT,
26
- device_token_hash TEXT NOT NULL,
27
- last_seen_at ${intType()},
28
- status TEXT NOT NULL,
29
- revoked_at ${intType()},
30
- created_at ${intType()} NOT NULL,
31
- updated_at ${intType()} NOT NULL
32
- )
33
- `),
34
- );
20
+ const createSql = `
21
+ CREATE TABLE IF NOT EXISTS integration_remote_devices (
22
+ id TEXT PRIMARY KEY,
23
+ owner_email TEXT NOT NULL,
24
+ org_id TEXT,
25
+ label TEXT NOT NULL,
26
+ platform TEXT,
27
+ app_version TEXT,
28
+ host_name TEXT,
29
+ metadata_json TEXT,
30
+ device_token_hash TEXT NOT NULL,
31
+ last_seen_at ${intType()},
32
+ status TEXT NOT NULL,
33
+ revoked_at ${intType()},
34
+ created_at ${intType()} NOT NULL,
35
+ updated_at ${intType()} NOT NULL
36
+ )
37
+ `;
38
+
39
+ if (isPostgres()) {
40
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
41
+ await ensureTableExists("integration_remote_devices", createSql);
42
+ await ensureColumnExists(
43
+ "integration_remote_devices",
44
+ "platform",
45
+ `ALTER TABLE integration_remote_devices ADD COLUMN IF NOT EXISTS platform TEXT`,
46
+ );
47
+ await ensureColumnExists(
48
+ "integration_remote_devices",
49
+ "app_version",
50
+ `ALTER TABLE integration_remote_devices ADD COLUMN IF NOT EXISTS app_version TEXT`,
51
+ );
52
+ await ensureColumnExists(
53
+ "integration_remote_devices",
54
+ "host_name",
55
+ `ALTER TABLE integration_remote_devices ADD COLUMN IF NOT EXISTS host_name TEXT`,
56
+ );
57
+ await ensureColumnExists(
58
+ "integration_remote_devices",
59
+ "metadata_json",
60
+ `ALTER TABLE integration_remote_devices ADD COLUMN IF NOT EXISTS metadata_json TEXT`,
61
+ );
62
+ await ensureColumnExists(
63
+ "integration_remote_devices",
64
+ "revoked_at",
65
+ `ALTER TABLE integration_remote_devices ADD COLUMN IF NOT EXISTS revoked_at ${intType()}`,
66
+ );
67
+ await ensureIndexExists(
68
+ "idx_remote_devices_token_hash",
69
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_devices_token_hash ON integration_remote_devices(device_token_hash)`,
70
+ );
71
+ await ensureIndexExists(
72
+ "idx_remote_devices_owner",
73
+ `CREATE INDEX IF NOT EXISTS idx_remote_devices_owner ON integration_remote_devices(owner_email, org_id)`,
74
+ );
75
+ return;
76
+ }
77
+ // SQLite (local dev): keep existing behavior verbatim
78
+ await retryOnDdlRace(() => client.execute(createSql));
35
79
  await addColumnIfMissing("platform", "TEXT");
36
80
  await addColumnIfMissing("app_version", "TEXT");
37
81
  await addColumnIfMissing("host_name", "TEXT");
@@ -1,4 +1,10 @@
1
- import { getDbExec, intType, retryOnDdlRace } from "../db/client.js";
1
+ import {
2
+ getDbExec,
3
+ isPostgres,
4
+ intType,
5
+ retryOnDdlRace,
6
+ } from "../db/client.js";
7
+ import { ensureTableExists, ensureIndexExists } from "../db/ddl-guard.js";
2
8
  import type {
3
9
  PublicRemotePushRegistration,
4
10
  RemotePushNotification,
@@ -7,29 +13,77 @@ import type {
7
13
 
8
14
  let _initPromise: Promise<void> | undefined;
9
15
 
16
+ // Build the CREATE SQL lazily (not at module scope) so intType() runs at
17
+ // RUNTIME, not import time — a module-scope call breaks any consumer whose
18
+ // db/client mock doesn't stub intType (e.g. db-admin specs).
19
+ function buildCreateRegistrationsSql(): string {
20
+ return `
21
+ CREATE TABLE IF NOT EXISTS integration_remote_push_registrations (
22
+ id TEXT PRIMARY KEY,
23
+ owner_email TEXT NOT NULL,
24
+ org_id TEXT,
25
+ provider TEXT NOT NULL,
26
+ platform TEXT,
27
+ client_device_id TEXT,
28
+ label TEXT,
29
+ token TEXT NOT NULL,
30
+ token_hash TEXT NOT NULL,
31
+ status TEXT NOT NULL,
32
+ last_seen_at ${intType()},
33
+ created_at ${intType()} NOT NULL,
34
+ updated_at ${intType()} NOT NULL
35
+ )
36
+ `;
37
+ }
38
+
39
+ function buildCreateNotificationsSql(): string {
40
+ return `
41
+ CREATE TABLE IF NOT EXISTS integration_remote_push_notifications (
42
+ id TEXT PRIMARY KEY,
43
+ owner_email TEXT NOT NULL,
44
+ org_id TEXT,
45
+ registration_id TEXT NOT NULL,
46
+ payload_json TEXT NOT NULL,
47
+ status TEXT NOT NULL,
48
+ attempts ${intType()} NOT NULL DEFAULT 0,
49
+ created_at ${intType()} NOT NULL,
50
+ updated_at ${intType()} NOT NULL
51
+ )
52
+ `;
53
+ }
54
+
10
55
  async function ensureTables(): Promise<void> {
11
56
  if (!_initPromise) {
12
57
  _initPromise = (async () => {
13
58
  const client = getDbExec();
14
- await retryOnDdlRace(() =>
15
- client.execute(`
16
- CREATE TABLE IF NOT EXISTS integration_remote_push_registrations (
17
- id TEXT PRIMARY KEY,
18
- owner_email TEXT NOT NULL,
19
- org_id TEXT,
20
- provider TEXT NOT NULL,
21
- platform TEXT,
22
- client_device_id TEXT,
23
- label TEXT,
24
- token TEXT NOT NULL,
25
- token_hash TEXT NOT NULL,
26
- status TEXT NOT NULL,
27
- last_seen_at ${intType()},
28
- created_at ${intType()} NOT NULL,
29
- updated_at ${intType()} NOT NULL
30
- )
31
- `),
32
- );
59
+ const createRegistrationsSql = buildCreateRegistrationsSql();
60
+ const createNotificationsSql = buildCreateNotificationsSql();
61
+ if (isPostgres()) {
62
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
63
+ await ensureTableExists(
64
+ "integration_remote_push_registrations",
65
+ createRegistrationsSql,
66
+ );
67
+ await ensureIndexExists(
68
+ "idx_remote_push_token_hash",
69
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_push_token_hash ON integration_remote_push_registrations(token_hash)`,
70
+ );
71
+ await ensureIndexExists(
72
+ "idx_remote_push_owner",
73
+ `CREATE INDEX IF NOT EXISTS idx_remote_push_owner ON integration_remote_push_registrations(owner_email, org_id, status)`,
74
+ );
75
+ await ensureTableExists(
76
+ "integration_remote_push_notifications",
77
+ createNotificationsSql,
78
+ );
79
+ await ensureIndexExists(
80
+ "idx_remote_push_notifications_owner",
81
+ `CREATE INDEX IF NOT EXISTS idx_remote_push_notifications_owner ON integration_remote_push_notifications(owner_email, org_id, status, created_at)`,
82
+ );
83
+ return;
84
+ }
85
+ // SQLite (local dev): keep existing behavior
86
+ await retryOnDdlRace(() => client.execute(createRegistrationsSql));
33
87
  await retryOnDdlRace(() =>
34
88
  client.execute(
35
89
  `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_push_token_hash ON integration_remote_push_registrations(token_hash)`,
@@ -41,21 +95,7 @@ async function ensureTables(): Promise<void> {
41
95
  ),
42
96
  );
43
97
 
44
- await retryOnDdlRace(() =>
45
- client.execute(`
46
- CREATE TABLE IF NOT EXISTS integration_remote_push_notifications (
47
- id TEXT PRIMARY KEY,
48
- owner_email TEXT NOT NULL,
49
- org_id TEXT,
50
- registration_id TEXT NOT NULL,
51
- payload_json TEXT NOT NULL,
52
- status TEXT NOT NULL,
53
- attempts ${intType()} NOT NULL DEFAULT 0,
54
- created_at ${intType()} NOT NULL,
55
- updated_at ${intType()} NOT NULL
56
- )
57
- `),
58
- );
98
+ await retryOnDdlRace(() => client.execute(createNotificationsSql));
59
99
  await retryOnDdlRace(() =>
60
100
  client.execute(
61
101
  `CREATE INDEX IF NOT EXISTS idx_remote_push_notifications_owner ON integration_remote_push_notifications(owner_email, org_id, status, created_at)`,
@@ -1,23 +1,49 @@
1
- import { getDbExec, intType, retryOnDdlRace } from "../db/client.js";
1
+ import {
2
+ getDbExec,
3
+ isPostgres,
4
+ intType,
5
+ retryOnDdlRace,
6
+ } from "../db/client.js";
7
+ import { ensureTableExists, ensureIndexExists } from "../db/ddl-guard.js";
2
8
  import type { RemoteRunEvent } from "./remote-types.js";
3
9
 
4
10
  let _initPromise: Promise<void> | undefined;
5
11
 
12
+ // Build the CREATE SQL lazily (not at module scope) so intType() runs at
13
+ // RUNTIME, not import time — a module-scope call breaks any consumer whose
14
+ // db/client mock doesn't stub intType (e.g. db-admin specs).
15
+ function buildCreateSql(): string {
16
+ return `
17
+ CREATE TABLE IF NOT EXISTS integration_remote_run_events (
18
+ device_id TEXT NOT NULL,
19
+ remote_run_id TEXT NOT NULL,
20
+ seq ${intType()} NOT NULL,
21
+ event_json TEXT NOT NULL,
22
+ created_at ${intType()} NOT NULL
23
+ )
24
+ `;
25
+ }
26
+
6
27
  async function ensureTable(): Promise<void> {
7
28
  if (!_initPromise) {
8
29
  _initPromise = (async () => {
9
30
  const client = getDbExec();
10
- await retryOnDdlRace(() =>
11
- client.execute(`
12
- CREATE TABLE IF NOT EXISTS integration_remote_run_events (
13
- device_id TEXT NOT NULL,
14
- remote_run_id TEXT NOT NULL,
15
- seq ${intType()} NOT NULL,
16
- event_json TEXT NOT NULL,
17
- created_at ${intType()} NOT NULL
18
- )
19
- `),
20
- );
31
+ const createSql = buildCreateSql();
32
+ if (isPostgres()) {
33
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
34
+ await ensureTableExists("integration_remote_run_events", createSql);
35
+ await ensureIndexExists(
36
+ "idx_remote_run_events_unique",
37
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_run_events_unique ON integration_remote_run_events(device_id, remote_run_id, seq)`,
38
+ );
39
+ await ensureIndexExists(
40
+ "idx_remote_run_events_run",
41
+ `CREATE INDEX IF NOT EXISTS idx_remote_run_events_run ON integration_remote_run_events(device_id, remote_run_id, seq)`,
42
+ );
43
+ return;
44
+ }
45
+ // SQLite (local dev): keep existing behavior
46
+ await retryOnDdlRace(() => client.execute(createSql));
21
47
  await retryOnDdlRace(() =>
22
48
  client.execute(
23
49
  `CREATE UNIQUE INDEX IF NOT EXISTS idx_remote_run_events_unique ON integration_remote_run_events(device_id, remote_run_id, seq)`,
@@ -1,4 +1,5 @@
1
1
  import { getDbExec, isPostgres, intType } from "../db/client.js";
2
+ import { ensureTableExists } from "../db/ddl-guard.js";
2
3
 
3
4
  let _initPromise: Promise<void> | undefined;
4
5
 
@@ -6,7 +7,7 @@ async function ensureTable(): Promise<void> {
6
7
  if (!_initPromise) {
7
8
  _initPromise = (async () => {
8
9
  const client = getDbExec();
9
- await client.execute(`
10
+ const createSql = `
10
11
  CREATE TABLE IF NOT EXISTS integration_thread_mappings (
11
12
  platform TEXT NOT NULL,
12
13
  external_thread_id TEXT NOT NULL,
@@ -16,7 +17,15 @@ async function ensureTable(): Promise<void> {
16
17
  updated_at ${intType()} NOT NULL,
17
18
  PRIMARY KEY (platform, external_thread_id)
18
19
  )
19
- `);
20
+ `;
21
+
22
+ if (isPostgres()) {
23
+ // PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
24
+ await ensureTableExists("integration_thread_mappings", createSql);
25
+ return;
26
+ }
27
+ // SQLite (local dev): keep existing behavior
28
+ await client.execute(createSql);
20
29
  })().catch((err) => {
21
30
  // Retry init on the next call after a failed startup.
22
31
  _initPromise = undefined;