@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/task-store.ts +25 -3
- package/corpus/core/src/agent/harness/store.ts +89 -55
- package/corpus/core/src/agent/observational-memory/store.ts +40 -14
- package/corpus/core/src/agent/run-store.ts +116 -59
- package/corpus/core/src/application-state/store.ts +36 -2
- package/corpus/core/src/audit/store.ts +37 -6
- package/corpus/core/src/browser-sessions/store.ts +31 -12
- package/corpus/core/src/chat-threads/store.ts +67 -3
- package/corpus/core/src/checkpoints/store.ts +14 -3
- package/corpus/core/src/cli/create.ts +9 -0
- package/corpus/core/src/collab/storage.ts +20 -2
- package/corpus/core/src/db/ddl-guard.ts +112 -0
- package/corpus/core/src/extensions/store.ts +76 -3
- package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
- package/corpus/core/src/integrations/config-store.ts +19 -18
- package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
- package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
- package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
- package/corpus/core/src/integrations/remote-push-store.ts +75 -35
- package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
- package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
- package/corpus/core/src/mcp/connect-store.ts +53 -16
- package/corpus/core/src/mcp/oauth-store.ts +32 -7
- package/corpus/core/src/notifications/store.ts +20 -4
- package/corpus/core/src/oauth-tokens/store.ts +45 -2
- package/corpus/core/src/observability/store.ts +154 -47
- package/corpus/core/src/progress/store.ts +36 -21
- package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
- package/corpus/core/src/provider-api/custom-registry.ts +15 -4
- package/corpus/core/src/provider-api/quota-governor.ts +15 -3
- package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
- package/corpus/core/src/resources/store.ts +66 -31
- package/corpus/core/src/secrets/storage.ts +18 -22
- package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
- package/corpus/core/src/server/auth.ts +22 -4
- package/corpus/core/src/server/better-auth-instance.ts +49 -15
- package/corpus/core/src/server/embed-session.ts +19 -4
- package/corpus/core/src/server/identity-sso-store.ts +40 -11
- package/corpus/core/src/server/recap-image-store.ts +28 -9
- package/corpus/core/src/settings/store.ts +14 -19
- package/corpus/core/src/usage/store.ts +51 -16
- package/corpus/core/src/workspace-connections/store.ts +200 -22
- package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
- package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
- package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
- package/corpus/templates/plan/app/global.css +6 -11
- package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
- package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +19 -3
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/agent/harness/store.d.ts.map +1 -1
- package/dist/agent/harness/store.js +63 -34
- package/dist/agent/harness/store.js.map +1 -1
- package/dist/agent/observational-memory/store.d.ts.map +1 -1
- package/dist/agent/observational-memory/store.js +30 -8
- package/dist/agent/observational-memory/store.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +103 -46
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -2
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/store.d.ts.map +1 -1
- package/dist/audit/store.js +18 -3
- package/dist/audit/store.js.map +1 -1
- package/dist/browser-sessions/store.d.ts.map +1 -1
- package/dist/browser-sessions/store.js +22 -7
- package/dist/browser-sessions/store.js.map +1 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +51 -3
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +12 -3
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +9 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/storage.d.ts.map +1 -1
- package/dist/collab/storage.js +14 -2
- package/dist/collab/storage.js.map +1 -1
- package/dist/db/ddl-guard.d.ts +63 -0
- package/dist/db/ddl-guard.d.ts.map +1 -1
- package/dist/db/ddl-guard.js +85 -0
- package/dist/db/ddl-guard.js.map +1 -1
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +32 -3
- package/dist/extensions/store.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +44 -24
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +17 -11
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +24 -16
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.d.ts.map +1 -1
- package/dist/integrations/remote-commands-store.js +29 -21
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.d.ts.map +1 -1
- package/dist/integrations/remote-devices-store.js +33 -18
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.d.ts.map +1 -1
- package/dist/integrations/remote-push-store.js +53 -31
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
- package/dist/integrations/remote-run-events-store.js +26 -10
- package/dist/integrations/remote-run-events-store.js.map +1 -1
- package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
- package/dist/integrations/thread-mapping-store.js +10 -2
- package/dist/integrations/thread-mapping-store.js.map +1 -1
- package/dist/mcp/connect-store.d.ts.map +1 -1
- package/dist/mcp/connect-store.js +34 -16
- package/dist/mcp/connect-store.js.map +1 -1
- package/dist/mcp/oauth-store.d.ts.map +1 -1
- package/dist/mcp/oauth-store.js +22 -7
- package/dist/mcp/oauth-store.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/notifications/store.d.ts.map +1 -1
- package/dist/notifications/store.js +15 -3
- package/dist/notifications/store.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +33 -2
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +72 -29
- package/dist/observability/store.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/progress/store.d.ts.map +1 -1
- package/dist/progress/store.js +31 -16
- package/dist/progress/store.js.map +1 -1
- package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
- package/dist/provider-api/corpus-jobs-store.js +16 -5
- package/dist/provider-api/corpus-jobs-store.js.map +1 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +14 -4
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/provider-api/quota-governor.d.ts.map +1 -1
- package/dist/provider-api/quota-governor.js +12 -3
- package/dist/provider-api/quota-governor.js.map +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
- package/dist/provider-api/staged-datasets-store.js +16 -5
- package/dist/provider-api/staged-datasets-store.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +56 -25
- package/dist/resources/store.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +10 -14
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
- package/dist/server/agent-teams-run-queue.js +15 -4
- package/dist/server/agent-teams-run-queue.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +16 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +55 -21
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +15 -4
- package/dist/server/embed-session.js.map +1 -1
- package/dist/server/identity-sso-store.d.ts.map +1 -1
- package/dist/server/identity-sso-store.js +30 -11
- package/dist/server/identity-sso-store.js.map +1 -1
- package/dist/server/recap-image-store.d.ts.map +1 -1
- package/dist/server/recap-image-store.js +21 -7
- package/dist/server/recap-image-store.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +11 -13
- package/dist/settings/store.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +36 -11
- package/dist/usage/store.js.map +1 -1
- package/dist/workspace-connections/store.d.ts.map +1 -1
- package/dist/workspace-connections/store.js +57 -7
- package/dist/workspace-connections/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
import { randomBytes, randomUUID, createHash } from "node:crypto";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
getDbExec,
|
|
13
|
+
isConnectionError,
|
|
14
|
+
intType,
|
|
15
|
+
isPostgres,
|
|
16
|
+
} from "../db/client.js";
|
|
17
|
+
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
12
18
|
|
|
13
19
|
let _initPromise: Promise<void> | undefined;
|
|
14
20
|
|
|
@@ -79,7 +85,7 @@ async function ensureTable(): Promise<void> {
|
|
|
79
85
|
if (!_initPromise) {
|
|
80
86
|
_initPromise = (async () => {
|
|
81
87
|
const client = getDbExec();
|
|
82
|
-
|
|
88
|
+
const createClientsSql = `
|
|
83
89
|
CREATE TABLE IF NOT EXISTS mcp_oauth_clients (
|
|
84
90
|
client_id TEXT PRIMARY KEY,
|
|
85
91
|
client_name TEXT,
|
|
@@ -89,8 +95,8 @@ async function ensureTable(): Promise<void> {
|
|
|
89
95
|
token_endpoint_auth_method TEXT,
|
|
90
96
|
created_at ${intType()}
|
|
91
97
|
)
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
`;
|
|
99
|
+
const createCodesSql = `
|
|
94
100
|
CREATE TABLE IF NOT EXISTS mcp_oauth_codes (
|
|
95
101
|
code TEXT PRIMARY KEY,
|
|
96
102
|
client_id TEXT NOT NULL,
|
|
@@ -106,8 +112,8 @@ async function ensureTable(): Promise<void> {
|
|
|
106
112
|
expires_at ${intType()},
|
|
107
113
|
consumed_at ${intType()}
|
|
108
114
|
)
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
`;
|
|
116
|
+
const createRefreshTokensSql = `
|
|
111
117
|
CREATE TABLE IF NOT EXISTS mcp_oauth_refresh_tokens (
|
|
112
118
|
id TEXT PRIMARY KEY,
|
|
113
119
|
token_hash TEXT UNIQUE NOT NULL,
|
|
@@ -123,7 +129,26 @@ async function ensureTable(): Promise<void> {
|
|
|
123
129
|
revoked_at ${intType()},
|
|
124
130
|
replaced_by_hash TEXT
|
|
125
131
|
)
|
|
126
|
-
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
if (isPostgres()) {
|
|
135
|
+
// PG-guard: probe information_schema first (no lock) and only issue
|
|
136
|
+
// DDL when the table is actually missing, wrapped in a
|
|
137
|
+
// transaction-scoped lock_timeout so a contended lock fails fast.
|
|
138
|
+
await ensureTableExists("mcp_oauth_clients", createClientsSql);
|
|
139
|
+
await ensureTableExists("mcp_oauth_codes", createCodesSql);
|
|
140
|
+
await ensureTableExists(
|
|
141
|
+
"mcp_oauth_refresh_tokens",
|
|
142
|
+
createRefreshTokensSql,
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
|
|
148
|
+
// create-then-execute behaviour.
|
|
149
|
+
await client.execute(createClientsSql);
|
|
150
|
+
await client.execute(createCodesSql);
|
|
151
|
+
await client.execute(createRefreshTokensSql);
|
|
127
152
|
})().catch((err) => {
|
|
128
153
|
_initPromise = undefined;
|
|
129
154
|
throw err;
|
|
@@ -3,9 +3,11 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import {
|
|
4
4
|
getDbExec,
|
|
5
5
|
intType,
|
|
6
|
+
isPostgres,
|
|
6
7
|
retryOnDdlRace,
|
|
7
8
|
safeJsonParse,
|
|
8
9
|
} from "../db/client.js";
|
|
10
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
9
11
|
import { recordChange } from "../server/poll.js";
|
|
10
12
|
import type { Notification, NotificationSeverity } from "./types.js";
|
|
11
13
|
|
|
@@ -24,8 +26,7 @@ async function ensureTable(): Promise<void> {
|
|
|
24
26
|
if (!_initPromise) {
|
|
25
27
|
_initPromise = (async () => {
|
|
26
28
|
const client = getDbExec();
|
|
27
|
-
|
|
28
|
-
client.execute(`
|
|
29
|
+
const createSql = `
|
|
29
30
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
30
31
|
id TEXT PRIMARY KEY,
|
|
31
32
|
owner TEXT NOT NULL,
|
|
@@ -37,8 +38,23 @@ async function ensureTable(): Promise<void> {
|
|
|
37
38
|
created_at ${intType()} NOT NULL,
|
|
38
39
|
read_at ${intType()}
|
|
39
40
|
)
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
if (isPostgres()) {
|
|
44
|
+
// PG-guard: probe information_schema / pg_indexes first (no lock) and
|
|
45
|
+
// only issue DDL when the table/index is actually missing, wrapped in
|
|
46
|
+
// a transaction-scoped lock_timeout so a contended lock fails fast.
|
|
47
|
+
await ensureTableExists("notifications", createSql);
|
|
48
|
+
await ensureIndexExists(
|
|
49
|
+
"idx_notifications_owner_unread",
|
|
50
|
+
`CREATE INDEX IF NOT EXISTS idx_notifications_owner_unread ON notifications (owner, read_at)`,
|
|
51
|
+
);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
|
|
56
|
+
// retryOnDdlRace behaviour.
|
|
57
|
+
await retryOnDdlRace(() => client.execute(createSql));
|
|
42
58
|
await retryOnDdlRace(() =>
|
|
43
59
|
client.execute(
|
|
44
60
|
`CREATE INDEX IF NOT EXISTS idx_notifications_owner_unread ON notifications (owner, read_at)`,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDbExec, isPostgres, intType } from "../db/client.js";
|
|
2
|
+
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
2
3
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
3
4
|
import {
|
|
4
5
|
encryptSecretValue,
|
|
@@ -54,7 +55,7 @@ async function ensureTable(): Promise<void> {
|
|
|
54
55
|
_initPromise = (async () => {
|
|
55
56
|
const client = getDbExec();
|
|
56
57
|
const table = oauthTokensTable();
|
|
57
|
-
|
|
58
|
+
const createSql = `
|
|
58
59
|
CREATE TABLE IF NOT EXISTS ${table} (
|
|
59
60
|
provider TEXT NOT NULL,
|
|
60
61
|
account_id TEXT NOT NULL,
|
|
@@ -63,7 +64,49 @@ async function ensureTable(): Promise<void> {
|
|
|
63
64
|
updated_at ${intType()} NOT NULL,
|
|
64
65
|
PRIMARY KEY (provider, account_id)
|
|
65
66
|
)
|
|
66
|
-
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
if (isPostgres()) {
|
|
70
|
+
// Hot path: the `oauth_tokens` table and its additive columns are
|
|
71
|
+
// virtually always already present in production. Issuing `CREATE
|
|
72
|
+
// TABLE`/`ALTER TABLE` still takes an ACCESS EXCLUSIVE lock that, in a
|
|
73
|
+
// fresh background-worker process behind a concurrent connection on the
|
|
74
|
+
// shared Neon DB, can block ~indefinitely. The ensure* wrappers probe
|
|
75
|
+
// `information_schema` first (plain reads, no lock) and run DDL ONLY for
|
|
76
|
+
// what is actually missing, bounded by a transaction-scoped
|
|
77
|
+
// `lock_timeout`. If a swallowed lock-timeout leaves the schema still
|
|
78
|
+
// missing they RE-PROBE and THROW rather than letting init memoize
|
|
79
|
+
// success against absent schema. `oauthTokensTable()` is
|
|
80
|
+
// `public.oauth_tokens` on Postgres; the wrappers take the unqualified
|
|
81
|
+
// table name.
|
|
82
|
+
await ensureTableExists("oauth_tokens", createSql);
|
|
83
|
+
// Migration: add owner column to existing tables — guarded so the hot
|
|
84
|
+
// path (column already present) skips the ACCESS EXCLUSIVE ALTER.
|
|
85
|
+
await ensureColumnExists(
|
|
86
|
+
"oauth_tokens",
|
|
87
|
+
"owner",
|
|
88
|
+
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS owner TEXT`,
|
|
89
|
+
);
|
|
90
|
+
// Migration: add display_name column
|
|
91
|
+
await ensureColumnExists(
|
|
92
|
+
"oauth_tokens",
|
|
93
|
+
"display_name",
|
|
94
|
+
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS display_name TEXT`,
|
|
95
|
+
);
|
|
96
|
+
// Backfill: set owner = account_id for existing rows without an owner
|
|
97
|
+
await client.execute(
|
|
98
|
+
`UPDATE ${table} SET owner = account_id WHERE owner IS NULL`,
|
|
99
|
+
);
|
|
100
|
+
// Older deployments have a 32-bit `updated_at`; on Postgres the
|
|
101
|
+
// `Date.now()` written on every token save overflows int4. Widen in place
|
|
102
|
+
// (no-op once done / on fresh DBs). Unqualified name — the helper scopes
|
|
103
|
+
// to the `public` schema.
|
|
104
|
+
await widenIntColumnsToBigInt("oauth_tokens", ["updated_at"]);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
109
|
+
await client.execute(createSql);
|
|
67
110
|
// Migration: add owner column to existing tables
|
|
68
111
|
try {
|
|
69
112
|
await client.execute(`ALTER TABLE ${table} ADD COLUMN owner TEXT`);
|
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
isPostgres,
|
|
13
13
|
retryOnDdlRace,
|
|
14
14
|
} from "../db/client.js";
|
|
15
|
+
import {
|
|
16
|
+
ensureTableExists,
|
|
17
|
+
ensureColumnExists,
|
|
18
|
+
ensureIndexExists,
|
|
19
|
+
} from "../db/ddl-guard.js";
|
|
15
20
|
import { isDuplicateColumnError } from "../db/migrations.js";
|
|
16
21
|
import type {
|
|
17
22
|
TraceSpan,
|
|
@@ -70,8 +75,7 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
70
75
|
_initPromise = (async () => {
|
|
71
76
|
const client = getDbExec();
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
client.execute(`
|
|
78
|
+
const traceSpansCreateSql = `
|
|
75
79
|
CREATE TABLE IF NOT EXISTS agent_trace_spans (
|
|
76
80
|
id TEXT PRIMARY KEY,
|
|
77
81
|
run_id TEXT NOT NULL,
|
|
@@ -91,11 +95,9 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
91
95
|
metadata TEXT,
|
|
92
96
|
created_at ${intType()} NOT NULL
|
|
93
97
|
)
|
|
94
|
-
|
|
95
|
-
);
|
|
98
|
+
`;
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
client.execute(`
|
|
100
|
+
const traceSummariesCreateSql = `
|
|
99
101
|
CREATE TABLE IF NOT EXISTS agent_trace_summaries (
|
|
100
102
|
run_id TEXT PRIMARY KEY,
|
|
101
103
|
thread_id TEXT,
|
|
@@ -112,11 +114,9 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
112
114
|
model TEXT NOT NULL DEFAULT '',
|
|
113
115
|
created_at ${intType()} NOT NULL
|
|
114
116
|
)
|
|
115
|
-
|
|
116
|
-
);
|
|
117
|
+
`;
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
client.execute(`
|
|
119
|
+
const feedbackCreateSql = `
|
|
120
120
|
CREATE TABLE IF NOT EXISTS agent_feedback (
|
|
121
121
|
id TEXT PRIMARY KEY,
|
|
122
122
|
run_id TEXT,
|
|
@@ -127,11 +127,9 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
127
127
|
user_id TEXT,
|
|
128
128
|
created_at ${intType()} NOT NULL
|
|
129
129
|
)
|
|
130
|
-
|
|
131
|
-
);
|
|
130
|
+
`;
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
client.execute(`
|
|
132
|
+
const satisfactionScoresCreateSql = `
|
|
135
133
|
CREATE TABLE IF NOT EXISTS agent_satisfaction_scores (
|
|
136
134
|
id TEXT PRIMARY KEY,
|
|
137
135
|
thread_id TEXT NOT NULL,
|
|
@@ -143,11 +141,9 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
143
141
|
length_trend_score REAL NOT NULL DEFAULT 0,
|
|
144
142
|
computed_at ${intType()} NOT NULL
|
|
145
143
|
)
|
|
146
|
-
|
|
147
|
-
);
|
|
144
|
+
`;
|
|
148
145
|
|
|
149
|
-
|
|
150
|
-
client.execute(`
|
|
146
|
+
const evalsCreateSql = `
|
|
151
147
|
CREATE TABLE IF NOT EXISTS agent_evals (
|
|
152
148
|
id TEXT PRIMARY KEY,
|
|
153
149
|
run_id TEXT NOT NULL,
|
|
@@ -160,11 +156,9 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
160
156
|
metadata TEXT,
|
|
161
157
|
created_at ${intType()} NOT NULL
|
|
162
158
|
)
|
|
163
|
-
|
|
164
|
-
);
|
|
159
|
+
`;
|
|
165
160
|
|
|
166
|
-
|
|
167
|
-
client.execute(`
|
|
161
|
+
const evalDatasetsCreateSql = `
|
|
168
162
|
CREATE TABLE IF NOT EXISTS agent_eval_datasets (
|
|
169
163
|
id TEXT PRIMARY KEY,
|
|
170
164
|
name TEXT NOT NULL,
|
|
@@ -173,11 +167,9 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
173
167
|
created_at ${intType()} NOT NULL,
|
|
174
168
|
updated_at ${intType()} NOT NULL
|
|
175
169
|
)
|
|
176
|
-
|
|
177
|
-
);
|
|
170
|
+
`;
|
|
178
171
|
|
|
179
|
-
|
|
180
|
-
client.execute(`
|
|
172
|
+
const experimentsCreateSql = `
|
|
181
173
|
CREATE TABLE IF NOT EXISTS agent_experiments (
|
|
182
174
|
id TEXT PRIMARY KEY,
|
|
183
175
|
name TEXT NOT NULL,
|
|
@@ -190,23 +182,9 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
190
182
|
created_at ${intType()} NOT NULL,
|
|
191
183
|
owner_email TEXT
|
|
192
184
|
)
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
// Additive migration for DBs created before the owner column shipped
|
|
196
|
-
// (any pre-existing rows have NULL owner — see `updateExperiment` for
|
|
197
|
-
// the migration semantics). Mutations on those rows fall back to the
|
|
198
|
-
// standard authentication gate but cannot enforce per-owner scoping
|
|
199
|
-
// until they're re-saved.
|
|
200
|
-
try {
|
|
201
|
-
await client.execute(
|
|
202
|
-
`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`,
|
|
203
|
-
);
|
|
204
|
-
} catch {
|
|
205
|
-
// Column already exists — expected after first run.
|
|
206
|
-
}
|
|
185
|
+
`;
|
|
207
186
|
|
|
208
|
-
|
|
209
|
-
client.execute(`
|
|
187
|
+
const experimentAssignmentsCreateSql = `
|
|
210
188
|
CREATE TABLE IF NOT EXISTS agent_experiment_assignments (
|
|
211
189
|
experiment_id TEXT NOT NULL,
|
|
212
190
|
user_id TEXT NOT NULL,
|
|
@@ -214,11 +192,9 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
214
192
|
assigned_at ${intType()} NOT NULL,
|
|
215
193
|
PRIMARY KEY (experiment_id, user_id)
|
|
216
194
|
)
|
|
217
|
-
|
|
218
|
-
);
|
|
195
|
+
`;
|
|
219
196
|
|
|
220
|
-
|
|
221
|
-
client.execute(`
|
|
197
|
+
const experimentResultsCreateSql = `
|
|
222
198
|
CREATE TABLE IF NOT EXISTS agent_experiment_results (
|
|
223
199
|
id TEXT PRIMARY KEY,
|
|
224
200
|
experiment_id TEXT NOT NULL,
|
|
@@ -230,9 +206,140 @@ export async function ensureObservabilityTables(): Promise<void> {
|
|
|
230
206
|
confidence_high REAL NOT NULL DEFAULT 0,
|
|
231
207
|
computed_at ${intType()} NOT NULL
|
|
232
208
|
)
|
|
233
|
-
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
if (isPostgres()) {
|
|
212
|
+
// PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
|
|
213
|
+
await ensureTableExists("agent_trace_spans", traceSpansCreateSql);
|
|
214
|
+
await ensureTableExists(
|
|
215
|
+
"agent_trace_summaries",
|
|
216
|
+
traceSummariesCreateSql,
|
|
217
|
+
);
|
|
218
|
+
await ensureTableExists("agent_feedback", feedbackCreateSql);
|
|
219
|
+
await ensureTableExists(
|
|
220
|
+
"agent_satisfaction_scores",
|
|
221
|
+
satisfactionScoresCreateSql,
|
|
222
|
+
);
|
|
223
|
+
await ensureTableExists("agent_evals", evalsCreateSql);
|
|
224
|
+
await ensureTableExists("agent_eval_datasets", evalDatasetsCreateSql);
|
|
225
|
+
await ensureTableExists("agent_experiments", experimentsCreateSql);
|
|
226
|
+
await ensureTableExists(
|
|
227
|
+
"agent_experiment_assignments",
|
|
228
|
+
experimentAssignmentsCreateSql,
|
|
229
|
+
);
|
|
230
|
+
await ensureTableExists(
|
|
231
|
+
"agent_experiment_results",
|
|
232
|
+
experimentResultsCreateSql,
|
|
233
|
+
);
|
|
234
|
+
await ensureColumnExists(
|
|
235
|
+
"agent_experiments",
|
|
236
|
+
"owner_email",
|
|
237
|
+
`ALTER TABLE agent_experiments ADD COLUMN IF NOT EXISTS owner_email TEXT`,
|
|
238
|
+
);
|
|
239
|
+
for (const table of USER_SCOPED_TABLES) {
|
|
240
|
+
await ensureColumnExists(
|
|
241
|
+
table,
|
|
242
|
+
"user_id",
|
|
243
|
+
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id TEXT`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
await ensureIndexExists(
|
|
247
|
+
"idx_trace_spans_run",
|
|
248
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`,
|
|
249
|
+
);
|
|
250
|
+
await ensureIndexExists(
|
|
251
|
+
"idx_trace_spans_thread",
|
|
252
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`,
|
|
253
|
+
);
|
|
254
|
+
await ensureIndexExists(
|
|
255
|
+
"idx_trace_spans_created",
|
|
256
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`,
|
|
257
|
+
);
|
|
258
|
+
await ensureIndexExists(
|
|
259
|
+
"idx_trace_summaries_created",
|
|
260
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`,
|
|
261
|
+
);
|
|
262
|
+
await ensureIndexExists(
|
|
263
|
+
"idx_trace_summaries_user",
|
|
264
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`,
|
|
265
|
+
);
|
|
266
|
+
await ensureIndexExists(
|
|
267
|
+
"idx_trace_spans_user",
|
|
268
|
+
`CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`,
|
|
269
|
+
);
|
|
270
|
+
await ensureIndexExists(
|
|
271
|
+
"idx_feedback_thread",
|
|
272
|
+
`CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`,
|
|
273
|
+
);
|
|
274
|
+
await ensureIndexExists(
|
|
275
|
+
"idx_feedback_created",
|
|
276
|
+
`CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`,
|
|
277
|
+
);
|
|
278
|
+
await ensureIndexExists(
|
|
279
|
+
"idx_feedback_user",
|
|
280
|
+
`CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`,
|
|
281
|
+
);
|
|
282
|
+
await ensureIndexExists(
|
|
283
|
+
"idx_satisfaction_thread",
|
|
284
|
+
`CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`,
|
|
285
|
+
);
|
|
286
|
+
await ensureIndexExists(
|
|
287
|
+
"idx_satisfaction_user",
|
|
288
|
+
`CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`,
|
|
289
|
+
);
|
|
290
|
+
await ensureIndexExists(
|
|
291
|
+
"idx_evals_run",
|
|
292
|
+
`CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`,
|
|
293
|
+
);
|
|
294
|
+
await ensureIndexExists(
|
|
295
|
+
"idx_evals_created",
|
|
296
|
+
`CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`,
|
|
297
|
+
);
|
|
298
|
+
await ensureIndexExists(
|
|
299
|
+
"idx_evals_user",
|
|
300
|
+
`CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`,
|
|
301
|
+
);
|
|
302
|
+
await ensureIndexExists(
|
|
303
|
+
"idx_experiment_results_exp",
|
|
304
|
+
`CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`,
|
|
305
|
+
);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
310
|
+
await retryOnDdlRace(() => client.execute(traceSpansCreateSql));
|
|
311
|
+
|
|
312
|
+
await retryOnDdlRace(() => client.execute(traceSummariesCreateSql));
|
|
313
|
+
|
|
314
|
+
await retryOnDdlRace(() => client.execute(feedbackCreateSql));
|
|
315
|
+
|
|
316
|
+
await retryOnDdlRace(() => client.execute(satisfactionScoresCreateSql));
|
|
317
|
+
|
|
318
|
+
await retryOnDdlRace(() => client.execute(evalsCreateSql));
|
|
319
|
+
|
|
320
|
+
await retryOnDdlRace(() => client.execute(evalDatasetsCreateSql));
|
|
321
|
+
|
|
322
|
+
await retryOnDdlRace(() => client.execute(experimentsCreateSql));
|
|
323
|
+
|
|
324
|
+
// Additive migration for DBs created before the owner column shipped
|
|
325
|
+
// (any pre-existing rows have NULL owner — see `updateExperiment` for
|
|
326
|
+
// the migration semantics). Mutations on those rows fall back to the
|
|
327
|
+
// standard authentication gate but cannot enforce per-owner scoping
|
|
328
|
+
// until they're re-saved.
|
|
329
|
+
try {
|
|
330
|
+
await client.execute(
|
|
331
|
+
`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`,
|
|
332
|
+
);
|
|
333
|
+
} catch {
|
|
334
|
+
// Column already exists — expected after first run.
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
await retryOnDdlRace(() =>
|
|
338
|
+
client.execute(experimentAssignmentsCreateSql),
|
|
234
339
|
);
|
|
235
340
|
|
|
341
|
+
await retryOnDdlRace(() => client.execute(experimentResultsCreateSql));
|
|
342
|
+
|
|
236
343
|
// Idempotent column upgrades for DBs created before per-user
|
|
237
344
|
// isolation. SQLite has no `ADD COLUMN IF NOT EXISTS`; Postgres
|
|
238
345
|
// surfaces "column ... already exists". `isDuplicateColumnError`
|
|
@@ -3,10 +3,11 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import {
|
|
4
4
|
getDbExec,
|
|
5
5
|
intType,
|
|
6
|
+
isPostgres,
|
|
6
7
|
isUniqueViolation,
|
|
7
|
-
retryOnDdlRace,
|
|
8
8
|
safeJsonParse,
|
|
9
9
|
} from "../db/client.js";
|
|
10
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
10
11
|
import { recordChange } from "../server/poll.js";
|
|
11
12
|
import type {
|
|
12
13
|
AgentRun,
|
|
@@ -42,32 +43,46 @@ async function ensureTable(): Promise<void> {
|
|
|
42
43
|
if (!_initPromise) {
|
|
43
44
|
_initPromise = (async () => {
|
|
44
45
|
const client = getDbExec();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
46
|
+
const createSql = `
|
|
47
|
+
CREATE TABLE IF NOT EXISTS progress_runs (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
owner TEXT NOT NULL,
|
|
50
|
+
title TEXT NOT NULL,
|
|
51
|
+
step TEXT,
|
|
52
|
+
percent ${intType()},
|
|
53
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
54
|
+
metadata TEXT,
|
|
55
|
+
started_at ${intType()} NOT NULL,
|
|
56
|
+
updated_at ${intType()} NOT NULL,
|
|
57
|
+
completed_at ${intType()}
|
|
58
|
+
)
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
if (isPostgres()) {
|
|
62
|
+
// PG-guard: probe information_schema / pg_indexes before issuing DDL to
|
|
63
|
+
// avoid ACCESS EXCLUSIVE lock contention in fresh background-worker processes.
|
|
64
|
+
await ensureTableExists("progress_runs", createSql);
|
|
65
|
+
await ensureIndexExists(
|
|
66
|
+
"idx_progress_runs_owner_status",
|
|
63
67
|
`CREATE INDEX IF NOT EXISTS idx_progress_runs_owner_status ON progress_runs (owner, status, started_at)`,
|
|
64
|
-
)
|
|
65
|
-
|
|
68
|
+
);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
66
73
|
// NOTE: table name is `progress_runs` (not `agent_runs`) to avoid
|
|
67
74
|
// colliding with core's existing agent/run-store.ts which uses
|
|
68
75
|
// `agent_runs` for agent-chat turn lifecycle tracking. These are
|
|
69
76
|
// separate concerns — progress = user-facing task status, agent_runs =
|
|
70
77
|
// internal chat turn bookkeeping.
|
|
78
|
+
await client.execute(createSql);
|
|
79
|
+
try {
|
|
80
|
+
await client.execute(
|
|
81
|
+
`CREATE INDEX IF NOT EXISTS idx_progress_runs_owner_status ON progress_runs (owner, status, started_at)`,
|
|
82
|
+
);
|
|
83
|
+
} catch {
|
|
84
|
+
// Index already exists or the dialect rejected a duplicate.
|
|
85
|
+
}
|
|
71
86
|
})().catch((err) => {
|
|
72
87
|
// Reset on failure so a transient DB outage doesn't poison the cached
|
|
73
88
|
// promise and reject every future insert/update call for the lifetime
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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)`,
|