From 214f85a1508124a0f4bb8b27f58496b67bf88ab0 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 25 Mar 2026 09:40:52 +0700 Subject: [PATCH] Phase 2a: Drizzle + PGlite database foundation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Drizzle schema for all four tables (projects, workspaces, agent_snapshots, agent_timeline_rows), PGlite bootstrap lifecycle, migration harness, and real contract tests proving query shapes work. No runtime authority changes — Session, AgentManager, and existing storage call sites are untouched. CLI cleanup removes stale projection fields from two fetchAgentTimeline call sites. Audit-verified: correctness (all acceptance criteria met), boundary (no runtime files touched), complexity (minor flags resolved). --- package-lock.json | 1077 +++++++++++++++++ packages/cli/src/commands/agent/run.ts | 1 - packages/cli/src/utils/timeline.ts | 1 - packages/server/drizzle.config.ts | 9 + packages/server/package.json | 3 + packages/server/src/server/db/migrations.ts | 11 + .../0000_youthful_lady_mastermind.sql | 56 + .../db/migrations/meta/0000_snapshot.json | 363 ++++++ .../server/db/migrations/meta/_journal.json | 13 + .../src/server/db/pglite-contract.test.ts | 264 ++++ .../server/src/server/db/pglite-database.ts | 24 + packages/server/src/server/db/schema.ts | 78 ++ 12 files changed, 1898 insertions(+), 2 deletions(-) create mode 100644 packages/server/drizzle.config.ts create mode 100644 packages/server/src/server/db/migrations.ts create mode 100644 packages/server/src/server/db/migrations/0000_youthful_lady_mastermind.sql create mode 100644 packages/server/src/server/db/migrations/meta/0000_snapshot.json create mode 100644 packages/server/src/server/db/migrations/meta/_journal.json create mode 100644 packages/server/src/server/db/pglite-contract.test.ts create mode 100644 packages/server/src/server/db/pglite-database.ts create mode 100644 packages/server/src/server/db/schema.ts diff --git a/package-lock.json b/package-lock.json index 9c5f1e32e7..7810e143ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3060,6 +3060,13 @@ "react": ">=16.8.0" } }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -3072,6 +3079,12 @@ "node": ">=0.8.0" } }, + "node_modules/@electric-sql/pglite": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", + "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", + "license": "Apache-2.0" + }, "node_modules/@electron/asar": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", @@ -3510,6 +3523,442 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -16324,6 +16773,631 @@ "url": "https://dotenvx.com" } }, + "node_modules/drizzle-kit": { + "version": "0.31.10", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", + "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.4", + "tsx": "^4.21.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/drizzle-orm": { + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.1.tgz", + "integrity": "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, "node_modules/dtrace-provider": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", @@ -35288,6 +36362,7 @@ "@ai-sdk/openai": "2.0.52", "@anthropic-ai/claude-agent-sdk": "^0.2.11", "@deepgram/sdk": "^3.4.0", + "@electric-sql/pglite": "^0.4.1", "@getpaseo/highlight": "0.1.33", "@getpaseo/relay": "0.1.33", "@modelcontextprotocol/sdk": "^1.20.1", @@ -35297,6 +36372,7 @@ "ai": "5.0.78", "ajv": "^8.17.1", "dotenv": "^17.2.3", + "drizzle-orm": "^0.45.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", "fast-uri": "^3.1.0", @@ -35326,6 +36402,7 @@ "@types/uuid": "^9.0.7", "@types/ws": "^8.5.8", "@vitest/ui": "^3.2.4", + "drizzle-kit": "^0.31.10", "playwright": "^1.56.1", "tsx": "^4.6.0", "typescript": "^5.2.2", diff --git a/packages/cli/src/commands/agent/run.ts b/packages/cli/src/commands/agent/run.ts index 040aae4e88..d7b8ffd2e2 100644 --- a/packages/cli/src/commands/agent/run.ts +++ b/packages/cli/src/commands/agent/run.ts @@ -193,7 +193,6 @@ export async function resolveStructuredResponseMessage(options: { try { const timeline = await options.client.fetchAgentTimeline(options.agentId, { direction: "tail", - projection: "projected", limit: 200, }); for (let index = timeline.entries.length - 1; index >= 0; index -= 1) { diff --git a/packages/cli/src/utils/timeline.ts b/packages/cli/src/utils/timeline.ts index e49b0244e5..2390b00a5a 100644 --- a/packages/cli/src/utils/timeline.ts +++ b/packages/cli/src/utils/timeline.ts @@ -11,7 +11,6 @@ export async function fetchProjectedTimelineItems( const timeline = await input.client.fetchAgentTimeline(input.agentId, { direction: "tail", limit: 0, - projection: "projected", }); return timeline.entries.map((entry) => entry.item); } diff --git a/packages/server/drizzle.config.ts b/packages/server/drizzle.config.ts new file mode 100644 index 0000000000..a32d2480b5 --- /dev/null +++ b/packages/server/drizzle.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + schema: "./packages/server/src/server/db/schema.ts", + out: "./packages/server/src/server/db/migrations", + dialect: "postgresql", + strict: true, + verbose: true, +}); diff --git a/packages/server/package.json b/packages/server/package.json index e36db18e8e..44ac124409 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -64,6 +64,7 @@ "@ai-sdk/openai": "2.0.52", "@anthropic-ai/claude-agent-sdk": "^0.2.11", "@deepgram/sdk": "^3.4.0", + "@electric-sql/pglite": "^0.4.1", "@getpaseo/highlight": "0.1.33", "@getpaseo/relay": "0.1.33", "@modelcontextprotocol/sdk": "^1.20.1", @@ -73,6 +74,7 @@ "ai": "5.0.78", "ajv": "^8.17.1", "dotenv": "^17.2.3", + "drizzle-orm": "^0.45.1", "express": "^4.18.2", "express-basic-auth": "^1.2.1", "fast-uri": "^3.1.0", @@ -96,6 +98,7 @@ }, "devDependencies": { "@playwright/test": "^1.56.1", + "drizzle-kit": "^0.31.10", "@types/express": "^4.17.20", "@types/node": "^20.9.0", "@types/qrcode": "^1.5.6", diff --git a/packages/server/src/server/db/migrations.ts b/packages/server/src/server/db/migrations.ts new file mode 100644 index 0000000000..74b548b7b3 --- /dev/null +++ b/packages/server/src/server/db/migrations.ts @@ -0,0 +1,11 @@ +import { fileURLToPath } from "node:url"; + +import type { PgliteDatabase } from "drizzle-orm/pglite"; +import { migrate } from "drizzle-orm/pglite/migrator"; + +const migrationsFolder = fileURLToPath(new URL("./migrations", import.meta.url)); + +export async function runPaseoDbMigrations(db: PgliteDatabase): Promise { + await migrate(db, { migrationsFolder }); +} + diff --git a/packages/server/src/server/db/migrations/0000_youthful_lady_mastermind.sql b/packages/server/src/server/db/migrations/0000_youthful_lady_mastermind.sql new file mode 100644 index 0000000000..e88634eb15 --- /dev/null +++ b/packages/server/src/server/db/migrations/0000_youthful_lady_mastermind.sql @@ -0,0 +1,56 @@ +CREATE TABLE "agent_snapshots" ( + "agent_id" text PRIMARY KEY NOT NULL, + "provider" text NOT NULL, + "workspace_id" text, + "cwd" text NOT NULL, + "created_at" text NOT NULL, + "updated_at" text NOT NULL, + "last_activity_at" text, + "last_user_message_at" text, + "title" text, + "labels" jsonb NOT NULL, + "last_status" text NOT NULL, + "last_mode_id" text, + "config" jsonb, + "runtime_info" jsonb, + "persistence" jsonb, + "requires_attention" boolean NOT NULL, + "attention_reason" text, + "attention_timestamp" text, + "internal" boolean NOT NULL, + "archived_at" text +); +--> statement-breakpoint +CREATE TABLE "agent_timeline_rows" ( + "agent_id" text NOT NULL, + "seq" integer NOT NULL, + "committed_at" text NOT NULL, + "item" jsonb NOT NULL, + "item_kind" text, + CONSTRAINT "agent_timeline_rows_pk" PRIMARY KEY("agent_id","seq") +); +--> statement-breakpoint +CREATE TABLE "projects" ( + "project_id" text PRIMARY KEY NOT NULL, + "root_path" text NOT NULL, + "kind" text NOT NULL, + "display_name" text NOT NULL, + "created_at" text NOT NULL, + "updated_at" text NOT NULL, + "archived_at" text +); +--> statement-breakpoint +CREATE TABLE "workspaces" ( + "workspace_id" text PRIMARY KEY NOT NULL, + "project_id" text NOT NULL, + "cwd" text NOT NULL, + "kind" text NOT NULL, + "display_name" text NOT NULL, + "created_at" text NOT NULL, + "updated_at" text NOT NULL, + "archived_at" text +); +--> statement-breakpoint +ALTER TABLE "agent_snapshots" ADD CONSTRAINT "agent_snapshots_workspace_id_workspaces_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("workspace_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_project_id_projects_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("project_id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +CREATE INDEX "workspaces_project_id_idx" ON "workspaces" USING btree ("project_id"); \ No newline at end of file diff --git a/packages/server/src/server/db/migrations/meta/0000_snapshot.json b/packages/server/src/server/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000000..f2a9a6710b --- /dev/null +++ b/packages/server/src/server/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,363 @@ +{ + "id": "5940c904-fd8b-4276-953d-acc382bf3fb8", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent_snapshots": { + "name": "agent_snapshots", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_user_message_at": { + "name": "last_user_message_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "last_status": { + "name": "last_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_mode_id": { + "name": "last_mode_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_info": { + "name": "runtime_info", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "persistence": { + "name": "persistence", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "requires_attention": { + "name": "requires_attention", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "attention_reason": { + "name": "attention_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attention_timestamp": { + "name": "attention_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal": { + "name": "internal", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_snapshots_workspace_id_workspaces_workspace_id_fk": { + "name": "agent_snapshots_workspace_id_workspaces_workspace_id_fk", + "tableFrom": "agent_snapshots", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "workspace_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_timeline_rows": { + "name": "agent_timeline_rows", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "committed_at": { + "name": "committed_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item": { + "name": "item", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "item_kind": { + "name": "item_kind", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "agent_timeline_rows_pk": { + "name": "agent_timeline_rows_pk", + "columns": [ + "agent_id", + "seq" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "root_path": { + "name": "root_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspaces": { + "name": "workspaces", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspaces_project_id_projects_project_id_fk": { + "name": "workspaces_project_id_projects_project_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "project_id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/server/src/server/db/migrations/meta/_journal.json b/packages/server/src/server/db/migrations/meta/_journal.json new file mode 100644 index 0000000000..c270812f4e --- /dev/null +++ b/packages/server/src/server/db/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1774405361702, + "tag": "0000_youthful_lady_mastermind", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/packages/server/src/server/db/pglite-contract.test.ts b/packages/server/src/server/db/pglite-contract.test.ts new file mode 100644 index 0000000000..2a04269364 --- /dev/null +++ b/packages/server/src/server/db/pglite-contract.test.ts @@ -0,0 +1,264 @@ +import os from "node:os"; +import path from "node:path"; +import { mkdtempSync, rmSync } from "node:fs"; + +import { and, asc, desc, eq, gt, lt, sql } from "drizzle-orm"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; + +import type { AgentTimelineItem } from "../agent/agent-sdk-types.js"; +import { openPaseoDatabase } from "./pglite-database.js"; +import { runPaseoDbMigrations } from "./migrations.js"; +import { + agentSnapshots, + agentTimelineRows, + projects, + workspaces, +} from "./schema.js"; + +function createTimestamp(day: number): string { + return `2026-03-${String(day).padStart(2, "0")}T00:00:00.000Z`; +} + +function createTimelineItem(type: AgentTimelineItem["type"], suffix: string): AgentTimelineItem { + if (type === "user_message") { + return { type, text: `user-${suffix}`, messageId: `msg-${suffix}` }; + } + if (type === "assistant_message" || type === "reasoning") { + return { type, text: `${type}-${suffix}` }; + } + return { type: "error", message: `error-${suffix}` }; +} + +describe("PGlite database contract", () => { + let tmpDir: string; + let dataDir: string; + + beforeEach(() => { + tmpDir = mkdtempSync(path.join(os.tmpdir(), "paseo-db-")); + dataDir = path.join(tmpDir, "db"); + }); + + afterEach(() => { + rmSync(tmpDir, { recursive: true, force: true }); + }); + + test("creates, migrates, closes, and reopens a persistent database", async () => { + const database = await openPaseoDatabase(dataDir); + + await database.db.insert(projects).values({ + projectId: "project-1", + rootPath: "/tmp/project-1", + kind: "git", + displayName: "Project One", + createdAt: createTimestamp(1), + updatedAt: createTimestamp(1), + archivedAt: null, + }); + + await database.close(); + + const reopened = await openPaseoDatabase(dataDir); + const rows = await reopened.db.select().from(projects); + expect(rows).toEqual([ + { + projectId: "project-1", + rootPath: "/tmp/project-1", + kind: "git", + displayName: "Project One", + createdAt: createTimestamp(1), + updatedAt: createTimestamp(1), + archivedAt: null, + }, + ]); + await reopened.close(); + }); + + test("supports project and workspace linkage plus archive field updates", async () => { + const database = await openPaseoDatabase(dataDir); + + await database.db.insert(projects).values({ + projectId: "project-1", + rootPath: "/tmp/project-1", + kind: "git", + displayName: "Project One", + createdAt: createTimestamp(1), + updatedAt: createTimestamp(1), + archivedAt: null, + }); + await database.db.insert(workspaces).values({ + workspaceId: "workspace-1", + projectId: "project-1", + cwd: "/tmp/project-1", + kind: "local_checkout", + displayName: "main", + createdAt: createTimestamp(1), + updatedAt: createTimestamp(1), + archivedAt: null, + }); + + await database.db + .update(workspaces) + .set({ archivedAt: createTimestamp(2), updatedAt: createTimestamp(2) }) + .where(eq(workspaces.workspaceId, "workspace-1")); + + const linkedRows = await database.db + .select({ + projectId: projects.projectId, + workspaceId: workspaces.workspaceId, + workspaceArchivedAt: workspaces.archivedAt, + }) + .from(workspaces) + .innerJoin(projects, eq(workspaces.projectId, projects.projectId)); + + expect(linkedRows).toEqual([ + { + projectId: "project-1", + workspaceId: "workspace-1", + workspaceArchivedAt: createTimestamp(2), + }, + ]); + + await database.close(); + }); + + test("supports snapshot insert, get, and update with the current persisted metadata shape", async () => { + const database = await openPaseoDatabase(dataDir); + + await database.db.insert(agentSnapshots).values({ + agentId: "agent-1", + provider: "codex", + workspaceId: null, + cwd: "/tmp/project-1", + createdAt: createTimestamp(1), + updatedAt: createTimestamp(1), + lastActivityAt: createTimestamp(1), + lastUserMessageAt: null, + title: "Agent One", + labels: { surface: "workspace" }, + lastStatus: "idle", + lastModeId: "plan", + config: { model: "gpt-5.1", modeId: "plan" }, + runtimeInfo: { provider: "codex", sessionId: "session-1" }, + persistence: { provider: "codex", sessionId: "session-1" }, + requiresAttention: false, + attentionReason: null, + attentionTimestamp: null, + internal: false, + archivedAt: null, + }); + + await database.db + .update(agentSnapshots) + .set({ + updatedAt: createTimestamp(2), + lastStatus: "running", + title: "Agent One Updated", + archivedAt: createTimestamp(3), + }) + .where(eq(agentSnapshots.agentId, "agent-1")); + + const rows = await database.db + .select() + .from(agentSnapshots) + .where(eq(agentSnapshots.agentId, "agent-1")); + + expect(rows).toEqual([ + { + agentId: "agent-1", + provider: "codex", + workspaceId: null, + cwd: "/tmp/project-1", + createdAt: createTimestamp(1), + updatedAt: createTimestamp(2), + lastActivityAt: createTimestamp(1), + lastUserMessageAt: null, + title: "Agent One Updated", + labels: { surface: "workspace" }, + lastStatus: "running", + lastModeId: "plan", + config: { model: "gpt-5.1", modeId: "plan" }, + runtimeInfo: { provider: "codex", sessionId: "session-1" }, + persistence: { provider: "codex", sessionId: "session-1" }, + requiresAttention: false, + attentionReason: null, + attentionTimestamp: null, + internal: false, + archivedAt: createTimestamp(3), + }, + ]); + + await database.close(); + }); + + test("supports timeline append and tail, after-seq, before-seq access patterns in committed order", async () => { + const database = await openPaseoDatabase(dataDir); + const rows = [1, 2, 3, 4].map((seq) => ({ + agentId: "agent-1", + seq, + committedAt: createTimestamp(seq), + item: createTimelineItem(seq === 1 ? "user_message" : "assistant_message", String(seq)), + itemKind: seq === 1 ? "user_message" : "assistant_message", + })); + + await database.db.insert(agentTimelineRows).values(rows); + + const tailRows = await database.db + .select() + .from(agentTimelineRows) + .where(eq(agentTimelineRows.agentId, "agent-1")) + .orderBy(desc(agentTimelineRows.seq)) + .limit(2); + + expect(tailRows.map((row) => row.seq).reverse()).toEqual([3, 4]); + + const afterRows = await database.db + .select() + .from(agentTimelineRows) + .where(and(eq(agentTimelineRows.agentId, "agent-1"), gt(agentTimelineRows.seq, 2))) + .orderBy(asc(agentTimelineRows.seq)); + + expect(afterRows.map((row) => row.seq)).toEqual([3, 4]); + + const beforeRows = await database.db + .select() + .from(agentTimelineRows) + .where(and(eq(agentTimelineRows.agentId, "agent-1"), lt(agentTimelineRows.seq, 4))) + .orderBy(desc(agentTimelineRows.seq)) + .limit(2); + + expect(beforeRows.map((row) => row.seq).reverse()).toEqual([2, 3]); + + await database.close(); + }); + + test("enforces per-agent seq uniqueness and reruns migrations without drift", async () => { + const database = await openPaseoDatabase(dataDir); + + await database.db.insert(agentTimelineRows).values({ + agentId: "agent-1", + seq: 1, + committedAt: createTimestamp(1), + item: createTimelineItem("assistant_message", "1"), + itemKind: "assistant_message", + }); + + await expect( + database.db.insert(agentTimelineRows).values({ + agentId: "agent-1", + seq: 1, + committedAt: createTimestamp(2), + item: createTimelineItem("assistant_message", "duplicate"), + itemKind: "assistant_message", + }), + ).rejects.toThrow(); + + await runPaseoDbMigrations(database.db); + + const migrationRows = await database.db.execute( + sql`select * from drizzle.__drizzle_migrations order by created_at`, + ); + expect(migrationRows.rows).toHaveLength(1); + + await database.close(); + }); +}); diff --git a/packages/server/src/server/db/pglite-database.ts b/packages/server/src/server/db/pglite-database.ts new file mode 100644 index 0000000000..8dca92e5bb --- /dev/null +++ b/packages/server/src/server/db/pglite-database.ts @@ -0,0 +1,24 @@ +import { PGlite } from "@electric-sql/pglite"; +import { drizzle } from "drizzle-orm/pglite"; + +import { runPaseoDbMigrations } from "./migrations.js"; +import { paseoDbSchema } from "./schema.js"; + +export interface PaseoDatabaseHandle { + client: PGlite; + db: ReturnType>; + close(): Promise; +} + +export async function openPaseoDatabase(dataDir: string): Promise { + const client = new PGlite(dataDir); + const db = drizzle(client, { schema: paseoDbSchema }); + await runPaseoDbMigrations(db); + return { + client, + db, + async close(): Promise { + await client.close(); + }, + }; +} diff --git a/packages/server/src/server/db/schema.ts b/packages/server/src/server/db/schema.ts new file mode 100644 index 0000000000..c432ce22ba --- /dev/null +++ b/packages/server/src/server/db/schema.ts @@ -0,0 +1,78 @@ +import { boolean, index, integer, jsonb, pgTable, primaryKey, text } from "drizzle-orm/pg-core"; + +import type { AgentPersistenceHandle, AgentRuntimeInfo, AgentTimelineItem } from "../agent/agent-sdk-types.js"; +import type { StoredAgentRecord } from "../agent/agent-storage.js"; + +export const projects = pgTable("projects", { + projectId: text("project_id").primaryKey(), + rootPath: text("root_path").notNull(), + kind: text("kind").notNull(), + displayName: text("display_name").notNull(), + createdAt: text("created_at").notNull(), + updatedAt: text("updated_at").notNull(), + archivedAt: text("archived_at"), +}); + +export const workspaces = pgTable( + "workspaces", + { + workspaceId: text("workspace_id").primaryKey(), + projectId: text("project_id") + .notNull() + .references(() => projects.projectId, { onDelete: "restrict", onUpdate: "cascade" }), + cwd: text("cwd").notNull(), + kind: text("kind").notNull(), + displayName: text("display_name").notNull(), + createdAt: text("created_at").notNull(), + updatedAt: text("updated_at").notNull(), + archivedAt: text("archived_at"), + }, + (table) => [index("workspaces_project_id_idx").on(table.projectId)], +); + +export const agentSnapshots = pgTable("agent_snapshots", { + agentId: text("agent_id").primaryKey(), + provider: text("provider").notNull(), + workspaceId: text("workspace_id").references(() => workspaces.workspaceId, { + onDelete: "set null", + onUpdate: "cascade", + }), + cwd: text("cwd").notNull(), + createdAt: text("created_at").notNull(), + updatedAt: text("updated_at").notNull(), + lastActivityAt: text("last_activity_at"), + lastUserMessageAt: text("last_user_message_at"), + title: text("title"), + labels: jsonb("labels").$type().notNull(), + lastStatus: text("last_status").notNull(), + lastModeId: text("last_mode_id"), + config: jsonb("config").$type(), + runtimeInfo: jsonb("runtime_info").$type(), + persistence: jsonb("persistence").$type(), + requiresAttention: boolean("requires_attention").notNull(), + attentionReason: text("attention_reason"), + attentionTimestamp: text("attention_timestamp"), + internal: boolean("internal").notNull(), + archivedAt: text("archived_at"), +}); + +export const agentTimelineRows = pgTable( + "agent_timeline_rows", + { + agentId: text("agent_id").notNull(), + seq: integer("seq").notNull(), + committedAt: text("committed_at").notNull(), + item: jsonb("item").$type().notNull(), + itemKind: text("item_kind"), + }, + (table) => [ + primaryKey({ columns: [table.agentId, table.seq], name: "agent_timeline_rows_pk" }), + ], +); + +export const paseoDbSchema = { + projects, + workspaces, + agentSnapshots, + agentTimelineRows, +};