@agent-native/core 0.77.8 → 0.77.10

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 (189) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +29 -0
  3. package/corpus/core/package.json +1 -1
  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/storage.ts +20 -2
  15. package/corpus/core/src/db/ddl-guard.ts +112 -0
  16. package/corpus/core/src/extensions/store.ts +76 -3
  17. package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
  18. package/corpus/core/src/integrations/config-store.ts +19 -18
  19. package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
  20. package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
  21. package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
  22. package/corpus/core/src/integrations/remote-push-store.ts +75 -35
  23. package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
  24. package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
  25. package/corpus/core/src/mcp/connect-store.ts +53 -16
  26. package/corpus/core/src/mcp/oauth-store.ts +32 -7
  27. package/corpus/core/src/notifications/store.ts +20 -4
  28. package/corpus/core/src/oauth-tokens/store.ts +45 -2
  29. package/corpus/core/src/observability/store.ts +154 -47
  30. package/corpus/core/src/progress/store.ts +36 -21
  31. package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
  32. package/corpus/core/src/provider-api/custom-registry.ts +15 -4
  33. package/corpus/core/src/provider-api/quota-governor.ts +15 -3
  34. package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
  35. package/corpus/core/src/resources/store.ts +66 -31
  36. package/corpus/core/src/secrets/storage.ts +18 -22
  37. package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
  38. package/corpus/core/src/server/auth.ts +22 -4
  39. package/corpus/core/src/server/better-auth-instance.ts +49 -15
  40. package/corpus/core/src/server/embed-session.ts +19 -4
  41. package/corpus/core/src/server/identity-sso-store.ts +40 -11
  42. package/corpus/core/src/server/recap-image-store.ts +28 -9
  43. package/corpus/core/src/settings/store.ts +14 -19
  44. package/corpus/core/src/usage/store.ts +51 -16
  45. package/corpus/core/src/workspace-connections/store.ts +200 -22
  46. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
  47. package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
  48. package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
  49. package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
  50. package/corpus/templates/plan/app/global.css +6 -11
  51. package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
  52. package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
  53. package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
  54. package/dist/a2a/task-store.d.ts.map +1 -1
  55. package/dist/a2a/task-store.js +19 -3
  56. package/dist/a2a/task-store.js.map +1 -1
  57. package/dist/agent/harness/store.d.ts.map +1 -1
  58. package/dist/agent/harness/store.js +63 -34
  59. package/dist/agent/harness/store.js.map +1 -1
  60. package/dist/agent/observational-memory/store.d.ts.map +1 -1
  61. package/dist/agent/observational-memory/store.js +30 -8
  62. package/dist/agent/observational-memory/store.js.map +1 -1
  63. package/dist/agent/run-store.d.ts.map +1 -1
  64. package/dist/agent/run-store.js +103 -46
  65. package/dist/agent/run-store.js.map +1 -1
  66. package/dist/application-state/store.d.ts.map +1 -1
  67. package/dist/application-state/store.js +28 -2
  68. package/dist/application-state/store.js.map +1 -1
  69. package/dist/audit/store.d.ts.map +1 -1
  70. package/dist/audit/store.js +18 -3
  71. package/dist/audit/store.js.map +1 -1
  72. package/dist/browser-sessions/store.d.ts.map +1 -1
  73. package/dist/browser-sessions/store.js +22 -7
  74. package/dist/browser-sessions/store.js.map +1 -1
  75. package/dist/chat-threads/store.d.ts.map +1 -1
  76. package/dist/chat-threads/store.js +51 -3
  77. package/dist/chat-threads/store.js.map +1 -1
  78. package/dist/checkpoints/store.d.ts.map +1 -1
  79. package/dist/checkpoints/store.js +12 -3
  80. package/dist/checkpoints/store.js.map +1 -1
  81. package/dist/cli/create.d.ts.map +1 -1
  82. package/dist/cli/create.js +9 -0
  83. package/dist/cli/create.js.map +1 -1
  84. package/dist/collab/routes.d.ts +2 -2
  85. package/dist/collab/storage.d.ts.map +1 -1
  86. package/dist/collab/storage.js +14 -2
  87. package/dist/collab/storage.js.map +1 -1
  88. package/dist/db/ddl-guard.d.ts +63 -0
  89. package/dist/db/ddl-guard.d.ts.map +1 -1
  90. package/dist/db/ddl-guard.js +85 -0
  91. package/dist/db/ddl-guard.js.map +1 -1
  92. package/dist/extensions/store.d.ts.map +1 -1
  93. package/dist/extensions/store.js +32 -3
  94. package/dist/extensions/store.js.map +1 -1
  95. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  96. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  97. package/dist/integrations/a2a-continuations-store.js +44 -24
  98. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  99. package/dist/integrations/config-store.d.ts.map +1 -1
  100. package/dist/integrations/config-store.js +17 -11
  101. package/dist/integrations/config-store.js.map +1 -1
  102. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  103. package/dist/integrations/pending-tasks-store.js +24 -16
  104. package/dist/integrations/pending-tasks-store.js.map +1 -1
  105. package/dist/integrations/remote-commands-store.d.ts.map +1 -1
  106. package/dist/integrations/remote-commands-store.js +29 -21
  107. package/dist/integrations/remote-commands-store.js.map +1 -1
  108. package/dist/integrations/remote-devices-store.d.ts.map +1 -1
  109. package/dist/integrations/remote-devices-store.js +33 -18
  110. package/dist/integrations/remote-devices-store.js.map +1 -1
  111. package/dist/integrations/remote-push-store.d.ts.map +1 -1
  112. package/dist/integrations/remote-push-store.js +53 -31
  113. package/dist/integrations/remote-push-store.js.map +1 -1
  114. package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
  115. package/dist/integrations/remote-run-events-store.js +26 -10
  116. package/dist/integrations/remote-run-events-store.js.map +1 -1
  117. package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
  118. package/dist/integrations/thread-mapping-store.js +10 -2
  119. package/dist/integrations/thread-mapping-store.js.map +1 -1
  120. package/dist/mcp/connect-store.d.ts.map +1 -1
  121. package/dist/mcp/connect-store.js +34 -16
  122. package/dist/mcp/connect-store.js.map +1 -1
  123. package/dist/mcp/oauth-store.d.ts.map +1 -1
  124. package/dist/mcp/oauth-store.js +22 -7
  125. package/dist/mcp/oauth-store.js.map +1 -1
  126. package/dist/notifications/routes.d.ts +2 -2
  127. package/dist/notifications/store.d.ts.map +1 -1
  128. package/dist/notifications/store.js +15 -3
  129. package/dist/notifications/store.js.map +1 -1
  130. package/dist/oauth-tokens/store.d.ts.map +1 -1
  131. package/dist/oauth-tokens/store.js +33 -2
  132. package/dist/oauth-tokens/store.js.map +1 -1
  133. package/dist/observability/routes.d.ts +7 -7
  134. package/dist/observability/store.d.ts.map +1 -1
  135. package/dist/observability/store.js +72 -29
  136. package/dist/observability/store.js.map +1 -1
  137. package/dist/progress/routes.d.ts +1 -1
  138. package/dist/progress/store.d.ts.map +1 -1
  139. package/dist/progress/store.js +31 -16
  140. package/dist/progress/store.js.map +1 -1
  141. package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
  142. package/dist/provider-api/corpus-jobs-store.js +16 -5
  143. package/dist/provider-api/corpus-jobs-store.js.map +1 -1
  144. package/dist/provider-api/custom-registry.d.ts.map +1 -1
  145. package/dist/provider-api/custom-registry.js +14 -4
  146. package/dist/provider-api/custom-registry.js.map +1 -1
  147. package/dist/provider-api/quota-governor.d.ts.map +1 -1
  148. package/dist/provider-api/quota-governor.js +12 -3
  149. package/dist/provider-api/quota-governor.js.map +1 -1
  150. package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
  151. package/dist/provider-api/staged-datasets-store.js +16 -5
  152. package/dist/provider-api/staged-datasets-store.js.map +1 -1
  153. package/dist/resources/handlers.d.ts +3 -3
  154. package/dist/resources/store.d.ts.map +1 -1
  155. package/dist/resources/store.js +56 -25
  156. package/dist/resources/store.js.map +1 -1
  157. package/dist/secrets/storage.d.ts.map +1 -1
  158. package/dist/secrets/storage.js +10 -14
  159. package/dist/secrets/storage.js.map +1 -1
  160. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  161. package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
  162. package/dist/server/agent-teams-run-queue.js +15 -4
  163. package/dist/server/agent-teams-run-queue.js.map +1 -1
  164. package/dist/server/auth.d.ts.map +1 -1
  165. package/dist/server/auth.js +16 -2
  166. package/dist/server/auth.js.map +1 -1
  167. package/dist/server/better-auth-instance.d.ts.map +1 -1
  168. package/dist/server/better-auth-instance.js +55 -21
  169. package/dist/server/better-auth-instance.js.map +1 -1
  170. package/dist/server/embed-session.d.ts.map +1 -1
  171. package/dist/server/embed-session.js +15 -4
  172. package/dist/server/embed-session.js.map +1 -1
  173. package/dist/server/identity-sso-store.d.ts.map +1 -1
  174. package/dist/server/identity-sso-store.js +30 -11
  175. package/dist/server/identity-sso-store.js.map +1 -1
  176. package/dist/server/recap-image-store.d.ts.map +1 -1
  177. package/dist/server/recap-image-store.js +21 -7
  178. package/dist/server/recap-image-store.js.map +1 -1
  179. package/dist/server/transcribe-voice.d.ts +1 -1
  180. package/dist/settings/store.d.ts.map +1 -1
  181. package/dist/settings/store.js +11 -13
  182. package/dist/settings/store.js.map +1 -1
  183. package/dist/usage/store.d.ts.map +1 -1
  184. package/dist/usage/store.js +36 -11
  185. package/dist/usage/store.js.map +1 -1
  186. package/dist/workspace-connections/store.d.ts.map +1 -1
  187. package/dist/workspace-connections/store.js +57 -7
  188. package/dist/workspace-connections/store.js.map +1 -1
  189. package/package.json +1 -1
@@ -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;
@@ -19,7 +19,13 @@
19
19
 
20
20
  import { randomBytes, randomUUID } from "node:crypto";
21
21
 
22
- import { getDbExec, isConnectionError, intType } from "../db/client.js";
22
+ import {
23
+ getDbExec,
24
+ isConnectionError,
25
+ intType,
26
+ isPostgres,
27
+ } from "../db/client.js";
28
+ import { ensureTableExists, ensureColumnExists } from "../db/ddl-guard.js";
23
29
 
24
30
  let _initPromise: Promise<void> | undefined;
25
31
 
@@ -59,7 +65,7 @@ async function ensureTable(): Promise<void> {
59
65
  const client = getDbExec();
60
66
  // Additive only. Never DROP / ALTER — this DB is shared across every
61
67
  // deploy context (preview/branch/prod) for hosted templates.
62
- await client.execute(`
68
+ const createTokensSql = `
63
69
  CREATE TABLE IF NOT EXISTS mcp_connect_tokens (
64
70
  id TEXT PRIMARY KEY,
65
71
  jti TEXT UNIQUE NOT NULL,
@@ -73,7 +79,50 @@ async function ensureTable(): Promise<void> {
73
79
  last_used_at ${intType()},
74
80
  revoked_at ${intType()}
75
81
  )
76
- `);
82
+ `;
83
+ const createDeviceCodesSql = `
84
+ CREATE TABLE IF NOT EXISTS mcp_device_codes (
85
+ device_code TEXT PRIMARY KEY,
86
+ user_code TEXT NOT NULL,
87
+ owner_email TEXT,
88
+ org_id TEXT,
89
+ status TEXT NOT NULL DEFAULT 'pending',
90
+ token_jti TEXT,
91
+ created_at ${intType()},
92
+ expires_at ${intType()},
93
+ consumed_at ${intType()}
94
+ )
95
+ `;
96
+
97
+ if (isPostgres()) {
98
+ // PG-guard: probe information_schema first (no lock) and only issue
99
+ // DDL when the table/column is actually missing, wrapped in a
100
+ // transaction-scoped lock_timeout so a contended lock fails fast.
101
+ await ensureTableExists("mcp_connect_tokens", createTokensSql);
102
+ // Additive columns for org service tokens — added after initial
103
+ // deployment; ensureColumnExists probes before ALTERing.
104
+ await ensureColumnExists(
105
+ "mcp_connect_tokens",
106
+ "kind",
107
+ `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'personal'`,
108
+ );
109
+ await ensureColumnExists(
110
+ "mcp_connect_tokens",
111
+ "service_name",
112
+ `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS service_name TEXT`,
113
+ );
114
+ await ensureColumnExists(
115
+ "mcp_connect_tokens",
116
+ "created_by",
117
+ `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS created_by TEXT`,
118
+ );
119
+ await ensureTableExists("mcp_device_codes", createDeviceCodesSql);
120
+ return;
121
+ }
122
+
123
+ // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
124
+ // create-then-additive-alter behaviour.
125
+ await client.execute(createTokensSql);
77
126
  // Additive columns for org service tokens (deployments that created the
78
127
  // table before these columns existed; fresh DBs get them via the CREATE
79
128
  // TABLE above). kind='personal' (default) preserves the original
@@ -107,19 +156,7 @@ async function ensureTable(): Promise<void> {
107
156
  }
108
157
  }
109
158
  }
110
- await client.execute(`
111
- CREATE TABLE IF NOT EXISTS mcp_device_codes (
112
- device_code TEXT PRIMARY KEY,
113
- user_code TEXT NOT NULL,
114
- owner_email TEXT,
115
- org_id TEXT,
116
- status TEXT NOT NULL DEFAULT 'pending',
117
- token_jti TEXT,
118
- created_at ${intType()},
119
- expires_at ${intType()},
120
- consumed_at ${intType()}
121
- )
122
- `);
159
+ await client.execute(createDeviceCodesSql);
123
160
  })().catch((err) => {
124
161
  // Don't cache a rejected init. A transient DB blip should let the next
125
162
  // connect/mint/revoke call retry rather than wedging the process.