@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +35 -0
- package/corpus/core/package.json +3 -3
- 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/awareness.ts +12 -9
- package/corpus/core/src/collab/client.ts +4 -2
- 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/clips/chrome-extension/package.json +1 -1
- package/corpus/templates/content/actions/list-trashed-content-databases.ts +36 -2
- package/corpus/templates/content/actions/move-document.ts +79 -33
- package/corpus/templates/content/actions/restore-content-database.ts +40 -3
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +84 -2
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +177 -49
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +393 -219
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +632 -467
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +114 -14
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +56 -15
- package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +6 -5
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +12 -4
- package/corpus/templates/content/app/global.css +9 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +36 -7
- package/corpus/templates/content/app/i18n-data.ts +140 -10
- package/corpus/templates/content/changelog/2026-06-25-comment-cards-now-track-their-highlighted-text-while-scrolli.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-comments-now-scroll-with-the-document-and-stay-below-the-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-creating-a-database-from-the-slash-menu-no-longer-leaves-sta.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-sidebar-hover-controls-no-longer-leave-a-lingering-fade-when.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-sidebar-page-actions-are-now-easier-to-see-when-hovering-ina.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-text-selections-in-the-editor-no-longer-show-white-gaps-when.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-has-a-one-click-page-link-copy-button.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-shows-page-breadcrumbs-and-the-latest.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-the-page-command-now-leaves-a-notion-style-page-reference-an.md +6 -0
- package/corpus/templates/content/shared/nfm.ts +16 -0
- 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/awareness.d.ts +1 -1
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/awareness.js +12 -8
- package/dist/collab/awareness.js.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +4 -3
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- 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/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/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/store.d.ts.map +1 -1
- package/dist/observability/store.js +72 -29
- package/dist/observability/store.js.map +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/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-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/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 +3 -3
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* `agent_audit_log`; reads are scoped to the caller's identity in SQL (no
|
|
8
8
|
* shares table — audit rows are never individually shared).
|
|
9
9
|
*/
|
|
10
|
-
import { getDbExec, intType,
|
|
10
|
+
import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
11
|
+
import { ensureTableExists, ensureIndexExists } from "../db/ddl-guard.js";
|
|
11
12
|
import type {
|
|
12
13
|
AuditEvent,
|
|
13
14
|
AuditQueryFilters,
|
|
@@ -20,9 +21,7 @@ export async function ensureAuditTables(): Promise<void> {
|
|
|
20
21
|
if (!_initPromise) {
|
|
21
22
|
_initPromise = (async () => {
|
|
22
23
|
const client = getDbExec();
|
|
23
|
-
|
|
24
|
-
await retryOnDdlRace(() =>
|
|
25
|
-
client.execute(`
|
|
24
|
+
const createSql = `
|
|
26
25
|
CREATE TABLE IF NOT EXISTS agent_audit_log (
|
|
27
26
|
id TEXT PRIMARY KEY,
|
|
28
27
|
created_at ${intType()} NOT NULL,
|
|
@@ -42,9 +41,41 @@ export async function ensureAuditTables(): Promise<void> {
|
|
|
42
41
|
owner_email TEXT,
|
|
43
42
|
visibility TEXT NOT NULL DEFAULT 'private'
|
|
44
43
|
)
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
if (isPostgres()) {
|
|
47
|
+
// PG-guard: probe information_schema / pg_indexes before issuing DDL to
|
|
48
|
+
// avoid ACCESS EXCLUSIVE lock contention in fresh background-worker processes.
|
|
49
|
+
await ensureTableExists("agent_audit_log", createSql);
|
|
50
|
+
await ensureIndexExists(
|
|
51
|
+
"idx_audit_owner",
|
|
52
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_owner ON agent_audit_log (owner_email, created_at)`,
|
|
53
|
+
);
|
|
54
|
+
await ensureIndexExists(
|
|
55
|
+
"idx_audit_org",
|
|
56
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_org ON agent_audit_log (org_id, created_at)`,
|
|
57
|
+
);
|
|
58
|
+
await ensureIndexExists(
|
|
59
|
+
"idx_audit_target",
|
|
60
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_target ON agent_audit_log (target_type, target_id, created_at)`,
|
|
61
|
+
);
|
|
62
|
+
await ensureIndexExists(
|
|
63
|
+
"idx_audit_turn",
|
|
64
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_turn ON agent_audit_log (turn_id)`,
|
|
65
|
+
);
|
|
66
|
+
await ensureIndexExists(
|
|
67
|
+
"idx_audit_actor",
|
|
68
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_actor ON agent_audit_log (actor_email, created_at)`,
|
|
69
|
+
);
|
|
70
|
+
await ensureIndexExists(
|
|
71
|
+
"idx_audit_created",
|
|
72
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_created ON agent_audit_log (created_at)`,
|
|
73
|
+
);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
47
76
|
|
|
77
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
78
|
+
await client.execute(createSql);
|
|
48
79
|
const indexes = [
|
|
49
80
|
`CREATE INDEX IF NOT EXISTS idx_audit_owner ON agent_audit_log (owner_email, created_at)`,
|
|
50
81
|
`CREATE INDEX IF NOT EXISTS idx_audit_org ON agent_audit_log (org_id, created_at)`,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
retryOnDdlRace,
|
|
6
6
|
safeJsonParse,
|
|
7
7
|
} from "../db/client.js";
|
|
8
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
8
9
|
import type {
|
|
9
10
|
AgentNativeBrowserSession,
|
|
10
11
|
AgentNativeBrowserSessionAction,
|
|
@@ -38,8 +39,7 @@ async function ensureTables(): Promise<void> {
|
|
|
38
39
|
if (!initPromise) {
|
|
39
40
|
initPromise = (async () => {
|
|
40
41
|
const client = getDbExec();
|
|
41
|
-
|
|
42
|
-
client.execute(`
|
|
42
|
+
const createSessionsSql = `
|
|
43
43
|
CREATE TABLE IF NOT EXISTS ${SESSION_TABLE} (
|
|
44
44
|
owner_email TEXT NOT NULL,
|
|
45
45
|
session_id TEXT NOT NULL,
|
|
@@ -53,16 +53,8 @@ async function ensureTables(): Promise<void> {
|
|
|
53
53
|
expires_at ${intType()} NOT NULL,
|
|
54
54
|
PRIMARY KEY (owner_email, session_id)
|
|
55
55
|
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
await retryOnDdlRace(() =>
|
|
59
|
-
client.execute(`
|
|
60
|
-
CREATE INDEX IF NOT EXISTS agent_native_browser_sessions_owner_seen_idx
|
|
61
|
-
ON ${SESSION_TABLE} (owner_email, last_seen_at)
|
|
62
|
-
`),
|
|
63
|
-
);
|
|
64
|
-
await retryOnDdlRace(() =>
|
|
65
|
-
client.execute(`
|
|
56
|
+
`;
|
|
57
|
+
const createRequestsSql = `
|
|
66
58
|
CREATE TABLE IF NOT EXISTS ${REQUEST_TABLE} (
|
|
67
59
|
owner_email TEXT NOT NULL,
|
|
68
60
|
session_id TEXT NOT NULL,
|
|
@@ -80,8 +72,35 @@ async function ensureTables(): Promise<void> {
|
|
|
80
72
|
error TEXT,
|
|
81
73
|
PRIMARY KEY (owner_email, request_id)
|
|
82
74
|
)
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
if (isPostgres()) {
|
|
78
|
+
// PG-guard: probe information_schema / pg_indexes first (no lock) and
|
|
79
|
+
// only issue DDL when the table/index is actually missing, wrapped in
|
|
80
|
+
// a transaction-scoped lock_timeout so a contended lock fails fast.
|
|
81
|
+
await ensureTableExists(SESSION_TABLE, createSessionsSql);
|
|
82
|
+
await ensureIndexExists(
|
|
83
|
+
"agent_native_browser_sessions_owner_seen_idx",
|
|
84
|
+
`CREATE INDEX IF NOT EXISTS agent_native_browser_sessions_owner_seen_idx ON ${SESSION_TABLE} (owner_email, last_seen_at)`,
|
|
85
|
+
);
|
|
86
|
+
await ensureTableExists(REQUEST_TABLE, createRequestsSql);
|
|
87
|
+
await ensureIndexExists(
|
|
88
|
+
"agent_native_browser_session_requests_pending_idx",
|
|
89
|
+
`CREATE INDEX IF NOT EXISTS agent_native_browser_session_requests_pending_idx ON ${REQUEST_TABLE} (owner_email, session_id, status, created_at)`,
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
|
|
95
|
+
// retryOnDdlRace behaviour.
|
|
96
|
+
await retryOnDdlRace(() => client.execute(createSessionsSql));
|
|
97
|
+
await retryOnDdlRace(() =>
|
|
98
|
+
client.execute(`
|
|
99
|
+
CREATE INDEX IF NOT EXISTS agent_native_browser_sessions_owner_seen_idx
|
|
100
|
+
ON ${SESSION_TABLE} (owner_email, last_seen_at)
|
|
83
101
|
`),
|
|
84
102
|
);
|
|
103
|
+
await retryOnDdlRace(() => client.execute(createRequestsSql));
|
|
85
104
|
await retryOnDdlRace(() =>
|
|
86
105
|
client.execute(`
|
|
87
106
|
CREATE INDEX IF NOT EXISTS agent_native_browser_session_requests_pending_idx
|
|
@@ -5,7 +5,12 @@ import {
|
|
|
5
5
|
normalizeThreadRepository,
|
|
6
6
|
normalizeThreadTitle,
|
|
7
7
|
} from "../agent/thread-data-builder.js";
|
|
8
|
-
import { getDbExec, intType } from "../db/client.js";
|
|
8
|
+
import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureColumnExists,
|
|
11
|
+
ensureIndexExists,
|
|
12
|
+
ensureTableExists,
|
|
13
|
+
} from "../db/ddl-guard.js";
|
|
9
14
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
10
15
|
import { emitChatThreadChange } from "./emitter.js";
|
|
11
16
|
|
|
@@ -53,7 +58,7 @@ async function ensureTable(): Promise<void> {
|
|
|
53
58
|
if (!_initPromise) {
|
|
54
59
|
_initPromise = (async () => {
|
|
55
60
|
const client = getDbExec();
|
|
56
|
-
|
|
61
|
+
const createSql = `
|
|
57
62
|
CREATE TABLE IF NOT EXISTS chat_threads (
|
|
58
63
|
id TEXT PRIMARY KEY,
|
|
59
64
|
owner_email TEXT NOT NULL,
|
|
@@ -69,7 +74,66 @@ async function ensureTable(): Promise<void> {
|
|
|
69
74
|
pinned_at ${intType()},
|
|
70
75
|
archived_at ${intType()}
|
|
71
76
|
)
|
|
72
|
-
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
if (isPostgres()) {
|
|
80
|
+
// Hot path: the `chat_threads` table and its indexes are virtually
|
|
81
|
+
// always already present in production. Issuing `CREATE TABLE`/
|
|
82
|
+
// `CREATE INDEX` still takes a lock that, in a fresh background-worker
|
|
83
|
+
// process behind a concurrent connection on the shared Neon DB, can
|
|
84
|
+
// block ~indefinitely (ACCESS EXCLUSIVE for CREATE TABLE; a write-
|
|
85
|
+
// blocking SHARE lock for CREATE INDEX). The ensure* wrappers probe
|
|
86
|
+
// `information_schema`/`pg_indexes` first (plain reads, no lock) and
|
|
87
|
+
// run DDL ONLY for what is actually missing, bounded by a transaction-
|
|
88
|
+
// scoped `lock_timeout`. If a swallowed lock-timeout leaves the schema
|
|
89
|
+
// still missing they RE-PROBE and THROW rather than letting init
|
|
90
|
+
// memoize success against absent schema. `chat_threads` is the
|
|
91
|
+
// unqualified name even though the table lives in `public`.
|
|
92
|
+
await ensureTableExists("chat_threads", createSql);
|
|
93
|
+
// Additive columns — guarded so the hot path (columns already present)
|
|
94
|
+
// skips the ACCESS EXCLUSIVE ALTER entirely.
|
|
95
|
+
for (const [col, type] of [
|
|
96
|
+
["scope_type", "TEXT"],
|
|
97
|
+
["scope_id", "TEXT"],
|
|
98
|
+
["scope_label", "TEXT"],
|
|
99
|
+
["pinned_at", intType()],
|
|
100
|
+
["archived_at", intType()],
|
|
101
|
+
] as const) {
|
|
102
|
+
await ensureColumnExists(
|
|
103
|
+
"chat_threads",
|
|
104
|
+
col,
|
|
105
|
+
`ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS ${col} ${type}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
// Widen millisecond-timestamp columns that older deployments created as
|
|
109
|
+
// 32-bit `INTEGER`; on Postgres the `Date.now()` written on every turn
|
|
110
|
+
// overflows int4. No-op once widened / on fresh BIGINT databases.
|
|
111
|
+
await widenIntColumnsToBigInt("chat_threads", [
|
|
112
|
+
"created_at",
|
|
113
|
+
"updated_at",
|
|
114
|
+
"pinned_at",
|
|
115
|
+
"archived_at",
|
|
116
|
+
]);
|
|
117
|
+
// Indexes for the hot read paths. Both the sidebar list and the
|
|
118
|
+
// scoped/per-resource list filter on owner_email (and optionally
|
|
119
|
+
// scope) and sort by updated_at. Probe pg_indexes first (no lock)
|
|
120
|
+
// and skip the SHARE-locking CREATE INDEX when already present.
|
|
121
|
+
await ensureIndexExists(
|
|
122
|
+
"chat_threads_owner_updated_idx",
|
|
123
|
+
`CREATE INDEX IF NOT EXISTS chat_threads_owner_updated_idx ON chat_threads (owner_email, updated_at)`,
|
|
124
|
+
);
|
|
125
|
+
await ensureIndexExists(
|
|
126
|
+
"chat_threads_scope_updated_idx",
|
|
127
|
+
`CREATE INDEX IF NOT EXISTS chat_threads_scope_updated_idx ON chat_threads (scope_type, scope_id, updated_at)`,
|
|
128
|
+
);
|
|
129
|
+
// One-time backfill of message_count for legacy rows written before
|
|
130
|
+
// the column was maintained.
|
|
131
|
+
await backfillLegacyMessageCounts(client);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
136
|
+
await client.execute(createSql);
|
|
73
137
|
// Additive migration for existing tables. Both SQLite and Postgres
|
|
74
138
|
// accept `ALTER TABLE ADD COLUMN` and will raise when the column
|
|
75
139
|
// already exists; the try/catch makes the call idempotent across
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getDbExec, intType } from "../db/client.js";
|
|
1
|
+
import { getDbExec, intType, isPostgres } 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 ensureCheckpointTable(): Promise<void> {
|
|
|
6
7
|
if (!_initPromise) {
|
|
7
8
|
_initPromise = (async () => {
|
|
8
9
|
const client = getDbExec();
|
|
9
|
-
|
|
10
|
+
const createSql = `
|
|
10
11
|
CREATE TABLE IF NOT EXISTS agent_checkpoints (
|
|
11
12
|
id TEXT PRIMARY KEY,
|
|
12
13
|
thread_id TEXT NOT NULL,
|
|
@@ -15,7 +16,17 @@ async function ensureCheckpointTable(): Promise<void> {
|
|
|
15
16
|
message TEXT NOT NULL DEFAULT '',
|
|
16
17
|
created_at ${intType()} NOT NULL
|
|
17
18
|
)
|
|
18
|
-
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
if (isPostgres()) {
|
|
22
|
+
// PG-guard: probe information_schema before issuing DDL to avoid ACCESS
|
|
23
|
+
// EXCLUSIVE lock contention in fresh background-worker processes.
|
|
24
|
+
await ensureTableExists("agent_checkpoints", createSql);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
29
|
+
await client.execute(createSql);
|
|
19
30
|
})().catch((err) => {
|
|
20
31
|
// Retry init on the next call after a failed startup.
|
|
21
32
|
_initPromise = undefined;
|
|
@@ -29,7 +29,16 @@ const STANDALONE_EXACT_DEPENDENCY_OVERRIDES: Record<string, string> = {
|
|
|
29
29
|
};
|
|
30
30
|
const SENTRY_MINIMUM_RELEASE_AGE_EXCLUDES = [
|
|
31
31
|
'"@sentry/browser"',
|
|
32
|
+
'"@sentry/browser-utils"',
|
|
33
|
+
'"@sentry/conventions"',
|
|
34
|
+
'"@sentry/core"',
|
|
35
|
+
'"@sentry/feedback"',
|
|
32
36
|
'"@sentry/node"',
|
|
37
|
+
'"@sentry/node-core"',
|
|
38
|
+
'"@sentry/opentelemetry"',
|
|
39
|
+
'"@sentry/replay"',
|
|
40
|
+
'"@sentry/replay-canvas"',
|
|
41
|
+
'"@sentry/server-utils"',
|
|
33
42
|
];
|
|
34
43
|
const FIRST_PARTY_TARBALL_SYMLINK_EXCLUDES = [
|
|
35
44
|
"*/CLAUDE.md",
|
|
@@ -102,7 +102,7 @@ function pruneIfEmpty(docId: string, map: Map<number, AwarenessEntry>): void {
|
|
|
102
102
|
*
|
|
103
103
|
* Client sends its awareness state and receives other clients' states.
|
|
104
104
|
*
|
|
105
|
-
* Body: { clientId: number, state: string (
|
|
105
|
+
* Body: { clientId: number, state: string | null (JSON-encoded awareness state, or null to clear) }
|
|
106
106
|
* Response: { states: Array<{ clientId: number, state: string }> }
|
|
107
107
|
*/
|
|
108
108
|
export const postAwareness = defineEventHandler(async (event: H3Event) => {
|
|
@@ -115,28 +115,31 @@ export const postAwareness = defineEventHandler(async (event: H3Event) => {
|
|
|
115
115
|
const body = await readBody(event);
|
|
116
116
|
const { clientId, state } = body as {
|
|
117
117
|
clientId?: number;
|
|
118
|
-
state?: string;
|
|
118
|
+
state?: string | null;
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
if (clientId == null ||
|
|
121
|
+
if (clientId == null || state === undefined) {
|
|
122
122
|
// `!clientId` would wrongly reject clientId === 0, which is a valid
|
|
123
|
-
// (if rare) Yjs client id.
|
|
123
|
+
// (if rare) Yjs client id. A null state is valid: it clears this client.
|
|
124
124
|
setResponseStatus(event, 400);
|
|
125
125
|
return { error: "clientId and state required" };
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
const map = getDocAwareness(docId);
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
if (state === null) {
|
|
131
|
+
map.delete(clientId);
|
|
132
|
+
} else {
|
|
133
|
+
// Store this client's state
|
|
134
|
+
map.set(clientId, { clientId, state, lastSeen: Date.now() });
|
|
135
|
+
}
|
|
132
136
|
|
|
133
137
|
// Clean expired entries, then prune the outer-map entry if it becomes empty.
|
|
134
138
|
// Without pruning, a deployment with many transient docIds (e.g. one per
|
|
135
139
|
// session) would grow _awarenessMap without bound.
|
|
136
140
|
cleanExpired(map);
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
// that was immediately evicted by a concurrent cleanExpired run).
|
|
141
|
+
// Null-state clears and expiry can empty the map; prune the outer entry so
|
|
142
|
+
// transient document ids do not accumulate.
|
|
140
143
|
pruneIfEmpty(docId, map);
|
|
141
144
|
|
|
142
145
|
// Build the full list of current states (all clients including sender).
|
|
@@ -276,11 +276,13 @@ function scheduleAwarenessPush(
|
|
|
276
276
|
const timer = setTimeout(() => {
|
|
277
277
|
_awarenessThrottleTimers.delete(key);
|
|
278
278
|
const state = getState();
|
|
279
|
-
if (!state) return;
|
|
280
279
|
fetch(`${baseUrl}/${docId}/awareness`, {
|
|
281
280
|
method: "POST",
|
|
282
281
|
headers: { "Content-Type": "application/json" },
|
|
283
|
-
body: JSON.stringify({
|
|
282
|
+
body: JSON.stringify({
|
|
283
|
+
clientId,
|
|
284
|
+
state: state ? JSON.stringify(state) : null,
|
|
285
|
+
}),
|
|
284
286
|
}).catch(() => {}); // best-effort; poll cycle is the baseline fallback
|
|
285
287
|
}, 150);
|
|
286
288
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { getDbExec, isPostgres } from "../db/client.js";
|
|
9
|
+
import { ensureTableExists, ensureColumnExists } from "../db/ddl-guard.js";
|
|
9
10
|
|
|
10
11
|
let _initPromise: Promise<void> | undefined;
|
|
11
12
|
|
|
@@ -14,7 +15,7 @@ async function ensureTable(): Promise<void> {
|
|
|
14
15
|
_initPromise = (async () => {
|
|
15
16
|
const client = getDbExec();
|
|
16
17
|
const nowDefault = isPostgres() ? "NOW()::text" : "datetime('now')";
|
|
17
|
-
|
|
18
|
+
const createSql = `
|
|
18
19
|
CREATE TABLE IF NOT EXISTS _collab_docs (
|
|
19
20
|
doc_id TEXT PRIMARY KEY,
|
|
20
21
|
yjs_state TEXT NOT NULL,
|
|
@@ -22,7 +23,24 @@ async function ensureTable(): Promise<void> {
|
|
|
22
23
|
version INTEGER NOT NULL DEFAULT 0,
|
|
23
24
|
updated_at TEXT NOT NULL DEFAULT (${nowDefault})
|
|
24
25
|
)
|
|
25
|
-
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
if (isPostgres()) {
|
|
29
|
+
// PG-guard: probe information_schema first (no lock) and only issue
|
|
30
|
+
// DDL when the table/column is actually missing, wrapped in a
|
|
31
|
+
// transaction-scoped lock_timeout so a contended lock fails fast.
|
|
32
|
+
await ensureTableExists("_collab_docs", createSql);
|
|
33
|
+
await ensureColumnExists(
|
|
34
|
+
"_collab_docs",
|
|
35
|
+
"version",
|
|
36
|
+
`ALTER TABLE _collab_docs ADD COLUMN IF NOT EXISTS version INTEGER NOT NULL DEFAULT 0`,
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
|
|
42
|
+
// create-then-additive-alter behaviour.
|
|
43
|
+
await client.execute(createSql);
|
|
26
44
|
try {
|
|
27
45
|
await client.execute(
|
|
28
46
|
`ALTER TABLE _collab_docs ADD COLUMN version INTEGER NOT NULL DEFAULT 0`,
|
|
@@ -121,6 +121,118 @@ export async function pgIndexExists(
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Probe → guarded-DDL → re-probe sequence for one piece of on-demand schema.
|
|
126
|
+
*
|
|
127
|
+
* This is the single safe primitive every `ensureTable()` should use so a
|
|
128
|
+
* swallowed `lock_timeout` can NEVER poison a store's init memo with missing
|
|
129
|
+
* schema. The flow:
|
|
130
|
+
*
|
|
131
|
+
* 1. `probe()` — cheap, lock-free existence check (pgTableExists / etc.).
|
|
132
|
+
* Already present → return `false` (nothing to do; the hot path takes NO
|
|
133
|
+
* lock).
|
|
134
|
+
* 2. Missing → run `ddl` through `runGuardedDdl` (bounded `lock_timeout`).
|
|
135
|
+
* - DDL completed → return `true`.
|
|
136
|
+
* - DDL was swallowed by a lock-timeout (`runGuardedDdl` returned `false`)
|
|
137
|
+
* → RE-PROBE. A concurrent connection virtually always created the
|
|
138
|
+
* object meanwhile, so the re-probe usually now reports it present →
|
|
139
|
+
* return `true`. But if it is STILL missing, the required schema does
|
|
140
|
+
* NOT exist and we must NOT let the caller memoize success — so THROW.
|
|
141
|
+
* The caller's `_initPromise` rejects and the next call retries instead
|
|
142
|
+
* of running forever against absent schema.
|
|
143
|
+
*
|
|
144
|
+
* On SQLite the probe is always `false` (helpers no-op there) and the SQLite
|
|
145
|
+
* branch of each store keeps its own create-then-catch behaviour, so this is a
|
|
146
|
+
* Postgres-path primitive in practice. Returns `true` when the object exists
|
|
147
|
+
* after this call (either pre-existing-after-timeout-race or freshly created),
|
|
148
|
+
* `false` only when it already existed up front (no DDL issued).
|
|
149
|
+
*/
|
|
150
|
+
export async function ensureSchemaObject(options: {
|
|
151
|
+
/** Lock-free existence check; `true` ⇒ already present, skip DDL. */
|
|
152
|
+
probe: () => Promise<boolean>;
|
|
153
|
+
/** DDL to run only when `probe()` reports the object missing. */
|
|
154
|
+
ddl: string;
|
|
155
|
+
/** Human-readable name of what's being ensured, for the error message. */
|
|
156
|
+
label: string;
|
|
157
|
+
/** Forwarded to `runGuardedDdl`. */
|
|
158
|
+
lockTimeout?: string;
|
|
159
|
+
/** Injectable client for tests. */
|
|
160
|
+
injectedClient?: DbExec;
|
|
161
|
+
}): Promise<boolean> {
|
|
162
|
+
const { probe, ddl, label, lockTimeout, injectedClient } = options;
|
|
163
|
+
if (await probe()) return false;
|
|
164
|
+
const ran = await runGuardedDdl(ddl, { lockTimeout, injectedClient });
|
|
165
|
+
if (ran) return true;
|
|
166
|
+
// The DDL was swallowed by a lock-timeout. The object is virtually always
|
|
167
|
+
// already correct by the time a contended boot retries (a concurrent
|
|
168
|
+
// connection created it), so re-probe before giving up.
|
|
169
|
+
if (await probe()) return true;
|
|
170
|
+
// Still missing after a swallowed timeout: do NOT memoize success with absent
|
|
171
|
+
// schema. Throw so the caller's init promise rejects and the next call
|
|
172
|
+
// retries, rather than leaving ensureTable "initialized" against a table/
|
|
173
|
+
// column/index that does not exist.
|
|
174
|
+
throw new Error(
|
|
175
|
+
`ensureSchemaObject: required schema "${label}" is still missing after a ` +
|
|
176
|
+
`lock-timed-out DDL; refusing to memoize init success. The next call will retry.`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Convenience wrapper: ensure a TABLE exists (probe via `pgTableExists`).
|
|
182
|
+
* No-op-returns `false` on SQLite (probe is always false there) — callers run
|
|
183
|
+
* the SQLite create on their own branch, so this is used on the Postgres path.
|
|
184
|
+
*/
|
|
185
|
+
export async function ensureTableExists(
|
|
186
|
+
table: string,
|
|
187
|
+
createSql: string,
|
|
188
|
+
options: { lockTimeout?: string; injectedClient?: DbExec } = {},
|
|
189
|
+
): Promise<boolean> {
|
|
190
|
+
return ensureSchemaObject({
|
|
191
|
+
probe: () => pgTableExists(table, options.injectedClient),
|
|
192
|
+
ddl: createSql,
|
|
193
|
+
label: `table ${table}`,
|
|
194
|
+
lockTimeout: options.lockTimeout,
|
|
195
|
+
injectedClient: options.injectedClient,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Convenience wrapper: ensure a COLUMN exists (probe via `pgColumnExists`).
|
|
201
|
+
* `addColumnSql` should be the full `ALTER TABLE … ADD COLUMN IF NOT EXISTS …`.
|
|
202
|
+
*/
|
|
203
|
+
export async function ensureColumnExists(
|
|
204
|
+
table: string,
|
|
205
|
+
column: string,
|
|
206
|
+
addColumnSql: string,
|
|
207
|
+
options: { lockTimeout?: string; injectedClient?: DbExec } = {},
|
|
208
|
+
): Promise<boolean> {
|
|
209
|
+
return ensureSchemaObject({
|
|
210
|
+
probe: () => pgColumnExists(table, column, options.injectedClient),
|
|
211
|
+
ddl: addColumnSql,
|
|
212
|
+
label: `column ${table}.${column}`,
|
|
213
|
+
lockTimeout: options.lockTimeout,
|
|
214
|
+
injectedClient: options.injectedClient,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Convenience wrapper: ensure an INDEX exists (probe via `pgIndexExists`).
|
|
220
|
+
* `createIndexSql` should be the full `CREATE INDEX IF NOT EXISTS <name> …`.
|
|
221
|
+
*/
|
|
222
|
+
export async function ensureIndexExists(
|
|
223
|
+
indexName: string,
|
|
224
|
+
createIndexSql: string,
|
|
225
|
+
options: { lockTimeout?: string; injectedClient?: DbExec } = {},
|
|
226
|
+
): Promise<boolean> {
|
|
227
|
+
return ensureSchemaObject({
|
|
228
|
+
probe: () => pgIndexExists(indexName, options.injectedClient),
|
|
229
|
+
ddl: createIndexSql,
|
|
230
|
+
label: `index ${indexName}`,
|
|
231
|
+
lockTimeout: options.lockTimeout,
|
|
232
|
+
injectedClient: options.injectedClient,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
124
236
|
/** True when an error looks like a Postgres `lock_timeout` (SQLSTATE 55P03). */
|
|
125
237
|
export function isLockTimeoutError(err: unknown): boolean {
|
|
126
238
|
const anyErr = err as { code?: unknown; message?: unknown } | null;
|
|
@@ -5,6 +5,11 @@ import { and, eq, gte, isNull } from "drizzle-orm";
|
|
|
5
5
|
import { appStatePut } from "../application-state/store.js";
|
|
6
6
|
import { getDbExec, isPostgres, retryOnDdlRace } from "../db/client.js";
|
|
7
7
|
import { createGetDb } from "../db/create-get-db.js";
|
|
8
|
+
import {
|
|
9
|
+
ensureTableExists,
|
|
10
|
+
ensureColumnExists,
|
|
11
|
+
ensureIndexExists,
|
|
12
|
+
} from "../db/ddl-guard.js";
|
|
8
13
|
import { recordChange } from "../server/poll.js";
|
|
9
14
|
import {
|
|
10
15
|
getRequestUserEmail,
|
|
@@ -77,6 +82,70 @@ export async function ensureExtensionsTables(): Promise<void> {
|
|
|
77
82
|
_initPromise = (async () => {
|
|
78
83
|
const client = getDbExec();
|
|
79
84
|
const pg = isPostgres();
|
|
85
|
+
if (pg) {
|
|
86
|
+
// PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
|
|
87
|
+
await ensureTableExists("tools", EXTENSIONS_CREATE_SQL_PG);
|
|
88
|
+
await migrateMisnamedExtensionsTable(client, pg); // data migration, not DDL — unchanged
|
|
89
|
+
await ensureTableExists("tool_shares", EXTENSION_SHARES_CREATE_SQL_PG);
|
|
90
|
+
await ensureTableExists("tool_data", EXTENSION_DATA_CREATE_SQL_PG);
|
|
91
|
+
await ensureExtensionDataItemId(client, pg); // ADD COLUMN — guarded inside
|
|
92
|
+
await ensureExtensionDataScope(client, pg); // ADD COLUMN — guarded inside
|
|
93
|
+
// DROP INDEX (for old index) — safe DDL, runs via client.execute; keep on both paths
|
|
94
|
+
await client.execute(EXTENSION_DATA_DROP_OLD_INDEX_SQL_PG);
|
|
95
|
+
await ensureIndexExists(
|
|
96
|
+
"tool_data_scoped_item_idx",
|
|
97
|
+
EXTENSION_DATA_ITEM_INDEX_SQL_PG,
|
|
98
|
+
);
|
|
99
|
+
await ensureIndexExists("tools_owner_idx", EXTENSIONS_OWNER_INDEX_SQL);
|
|
100
|
+
await ensureIndexExists("tools_org_idx", EXTENSIONS_ORG_INDEX_SQL);
|
|
101
|
+
await ensureIndexExists(
|
|
102
|
+
"tools_updated_at_idx",
|
|
103
|
+
EXTENSIONS_UPDATED_INDEX_SQL,
|
|
104
|
+
);
|
|
105
|
+
await ensureExtensionsGlobalHideColumns(client, pg); // ADD COLUMN — guarded inside
|
|
106
|
+
await ensureIndexExists(
|
|
107
|
+
"tools_hidden_at_idx",
|
|
108
|
+
EXTENSIONS_HIDDEN_AT_INDEX_SQL,
|
|
109
|
+
);
|
|
110
|
+
await ensureIndexExists(
|
|
111
|
+
"tool_shares_resource_idx",
|
|
112
|
+
EXTENSION_SHARES_RESOURCE_INDEX_SQL,
|
|
113
|
+
);
|
|
114
|
+
await ensureTableExists(
|
|
115
|
+
"tool_hidden_extensions",
|
|
116
|
+
EXTENSION_HIDES_CREATE_SQL_PG,
|
|
117
|
+
);
|
|
118
|
+
await ensureIndexExists(
|
|
119
|
+
"tool_hidden_extensions_user_tool_idx",
|
|
120
|
+
EXTENSION_HIDES_UNIQUE_INDEX_SQL,
|
|
121
|
+
);
|
|
122
|
+
await ensureIndexExists(
|
|
123
|
+
"tool_hidden_extensions_owner_idx",
|
|
124
|
+
EXTENSION_HIDES_OWNER_INDEX_SQL,
|
|
125
|
+
);
|
|
126
|
+
await ensureTableExists(
|
|
127
|
+
"tool_history",
|
|
128
|
+
EXTENSION_HISTORY_CREATE_SQL_PG,
|
|
129
|
+
);
|
|
130
|
+
await ensureIndexExists(
|
|
131
|
+
"tool_history_tool_version_idx",
|
|
132
|
+
EXTENSION_HISTORY_VERSION_INDEX_SQL,
|
|
133
|
+
);
|
|
134
|
+
await ensureIndexExists(
|
|
135
|
+
"tool_history_tool_created_idx",
|
|
136
|
+
EXTENSION_HISTORY_CREATED_INDEX_SQL,
|
|
137
|
+
);
|
|
138
|
+
await ensureTableExists(
|
|
139
|
+
"tool_consents",
|
|
140
|
+
EXTENSION_CONSENTS_CREATE_SQL_PG,
|
|
141
|
+
);
|
|
142
|
+
await ensureIndexExists(
|
|
143
|
+
"tool_consents_viewer_idx",
|
|
144
|
+
EXTENSION_CONSENTS_VIEWER_INDEX_SQL,
|
|
145
|
+
);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// SQLite (local dev): keep existing behavior
|
|
80
149
|
await retryOnDdlRace(() =>
|
|
81
150
|
client.execute(pg ? EXTENSIONS_CREATE_SQL_PG : EXTENSIONS_CREATE_SQL),
|
|
82
151
|
);
|
|
@@ -192,7 +261,9 @@ async function ensureExtensionDataItemId(
|
|
|
192
261
|
pg: boolean,
|
|
193
262
|
): Promise<void> {
|
|
194
263
|
if (pg) {
|
|
195
|
-
await
|
|
264
|
+
await ensureColumnExists(
|
|
265
|
+
"tool_data",
|
|
266
|
+
"item_id",
|
|
196
267
|
`ALTER TABLE tool_data ADD COLUMN IF NOT EXISTS item_id TEXT`,
|
|
197
268
|
);
|
|
198
269
|
return;
|
|
@@ -219,7 +290,9 @@ async function ensureExtensionDataScope(
|
|
|
219
290
|
): Promise<void> {
|
|
220
291
|
const addCol = (name: string, def: string) => {
|
|
221
292
|
if (pg) {
|
|
222
|
-
return
|
|
293
|
+
return ensureColumnExists(
|
|
294
|
+
"tool_data",
|
|
295
|
+
name,
|
|
223
296
|
`ALTER TABLE tool_data ADD COLUMN IF NOT EXISTS ${name} ${def}`,
|
|
224
297
|
);
|
|
225
298
|
}
|
|
@@ -255,7 +328,7 @@ async function ensureExtensionsGlobalHideColumns(
|
|
|
255
328
|
// does not, so we drop the clause and swallow the duplicate-column error.
|
|
256
329
|
const addCol = (pgSql: string, name: string, def: string) => {
|
|
257
330
|
if (pg) {
|
|
258
|
-
return
|
|
331
|
+
return ensureColumnExists("tools", name, pgSql);
|
|
259
332
|
}
|
|
260
333
|
return client
|
|
261
334
|
.execute(`ALTER TABLE tools ADD COLUMN ${name} ${def}`)
|